/* * SatoChip Bitcoin Hardware Wallet based on javacard * (c) 2015 by Toporin - 16DMCk4WUaHofchAhpMaQS4UPm4urcy2dN * Sources available on https://github.com/Toporin * Changes include: -partial support for Bip32 (only hardened keys) * -simple Bitcoin transaction signatures * -Bitcoin message signatures * * * Based on the M.US.C.L.E framework: * see http://pcsclite.alioth.debian.org/musclecard.com/musclecard/ * see https://github.com/martinpaljak/MuscleApplet/blob/d005f36209bdd7020bac0d783b228243126fd2f8/src/com/musclecard/CardEdge/CardEdge.java * * MUSCLE SmartCard Development * Authors: Tommaso Cucinotta <cucinotta@sssup.it> * David Corcoran <corcoran@linuxnet.com> * Description: CardEdge implementation with JavaCard * Protocol Authors: Tommaso Cucinotta <cucinotta@sssup.it> * David Corcoran <corcoran@linuxnet.com> * * BEGIN LICENSE BLOCK * Copyright (C) 1999-2002 David Corcoran <corcoran@linuxnet.com> * Copyright (C) 2015 Toporin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * Changes to this license can be made only by the copyright author with * explicit written consent. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Alternatively, the contents of this file may be used under the terms of * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which * case the provisions of the LGPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms * of the LGPL, and not to allow others to use your version of this file * under the terms of the BSD license, indicate your decision by deleting * the provisions above and replace them with the notice and other * provisions required by the LGPL. If you do not delete the provisions * above, a recipient may use your version of this file under the terms of * either the BSD license or the LGPL. * * 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 St, Fifth Floor, Boston, MA 02110-1301 USA * END LICENSE_BLOCK * */ package org.satochip.applet; import javacard.framework.APDU; import javacard.framework.CardRuntimeException; 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.AESKey; import javacard.security.DESKey; import javacard.security.ECPrivateKey; import javacard.security.ECPublicKey; //import javacard.security.HMACKey; import javacard.security.Key; import javacard.security.KeyAgreement; import javacard.security.KeyBuilder; import javacard.security.KeyPair; import javacard.security.RSAPrivateCrtKey; import javacard.security.RSAPrivateKey; import javacard.security.RSAPublicKey; import javacard.security.RandomData; import javacard.security.Signature; import javacard.security.MessageDigest; import javacardx.apdu.ExtendedLength; //debugXL 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 */ /** * VERSION HISTORY * PROTOCOL VERSION: changes that impact compatibility with the client side * APPLET VERSION: changes with no impact on compatibility of the client */ // 0.1-0.1: initial version // 0.2-0.1: support for hmac-sha1 authorization + improved sha256 + bip32 full suport // 0.3-0.1: change parseTransaction response for coherence: add separate short flags for second factor authentication // 0.3-0.2: fast Sha512 computation private final static byte PROTOCOL_MAJOR_VERSION = (byte) 0; private final static byte PROTOCOL_MINOR_VERSION = (byte) 3; private final static byte APPLET_MAJOR_VERSION = (byte) 0; private final static byte APPLET_MINOR_VERSION = (byte) 2; // Maximum number of keys handled by the Cardlet private final static byte MAX_NUM_KEYS = (byte) 16; // 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; private final static short TMP_BUFFER_SIZE = (short) 256; // Minimum PIN size private final static byte PIN_MIN_SIZE = (byte) 4; // Maximum PIN size private final static byte PIN_MAX_SIZE = (byte) 16; // PIN[0] initial value... private final static byte[] PIN_INIT_VALUE={(byte)'M',(byte)'u',(byte)'s',(byte)'c',(byte)'l',(byte)'e',(byte)'0',(byte)'0'}; // 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; private final static byte ACL_READ = (byte) 0; private final static byte ACL_WRITE = (byte) 2; private final static byte ACL_USE = (byte) 4; // 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_GEN_KEYSYM = (byte) 0x31; private final static byte INS_IMPORT_KEY = (byte) 0x32; //private final static byte INS_EXPORT_KEY = (byte) 0x34; private final static byte INS_GET_PUBLIC_FROM_PRIVATE= (byte)0x35; private final static byte INS_COMPUTE_CRYPT = (byte) 0x36; private final static byte INS_COMPUTE_SIGN = (byte) 0x37; // added // 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; private final static byte INS_SIZE_OBJ = (byte) 0x57; // 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; // HD wallet private final static byte INS_COMPUTE_SHA512 = (byte) 0x6A; //private final static byte INS_COMPUTE_HMACSHA512= (byte) 0x6B; private final static byte INS_COMPUTE_HMAC= (byte) 0x6B; private final static byte INS_BIP32_IMPORT_SEED= (byte) 0x6C; private final static byte INS_BIP32_GET_AUTHENTIKEY= (byte) 0x73; private final static byte INS_BIP32_GET_EXTENDED_KEY= (byte) 0x6D; private final static byte INS_BIP32_SET_EXTENDED_KEY= (byte) 0x70; private final static byte INS_SIGN_MESSAGE= (byte) 0x6E; private final static byte INS_SIGN_SHORT_MESSAGE= (byte) 0x72; private final static byte INS_SIGN_TRANSACTION= (byte) 0x6F; private final static byte INS_PARSE_TRANSACTION = (byte) 0x71; // debug private final static byte INS_TEST_SHA1 = (byte) 0x80; /** 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 setup is not not done */ private final static short SW_SETUP_NOT_DONE = (short) 0x9C04; /** 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; /** For debugging purposes 2*/ private final static short SW_DEBUG_FLAG = (short) 0x9FFF; /** Very low probability error */ private final static short SW_BIP32_DERIVATION_ERROR = (short) 0x9C0E; /** Support only hardened key currently */ private final static short SW_BIP32_HARDENED_KEY_ERROR = (short) 0x9C16; /** Incorrect initialization of method */ private final static short SW_INCORRECT_INITIALIZATION = (short) 0x9C13; /** Bip32 seed is not initialized*/ private final static short SW_BIP32_UNINITIALIZED_SEED = (short) 0x9C14; /** Incorrect transaction hash */ private final static short SW_INCORRECT_TXHASH = (short) 0x9C15; // 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 Modes admitted in ComputeCrypt() 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_EC_SECP256k1 = (byte) 0x03; // EC: provide P, a, b, G, R, K 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); // JC API 2.2.2 does not define these constants: private final static byte ALG_ECDSA_SHA_256= (byte) 33; private final static byte ALG_EC_SVDP_DH_PLAIN= (byte) 3; //https://javacard.kenai.com/javadocs/connected/javacard/security/KeyAgreement.html#ALG_EC_SVDP_DH_PLAIN private final static short LENGTH_EC_FP_256= (short) 256; /**************************************** * Instance variables declaration * ****************************************/ // Memory & Object Manager private MemoryManager mem; 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; // PIN and PUK objects, allocated on demand private OwnerPIN[] pins, ublk_pins; // Buffer for storing extended APDUs private byte[] recvBuffer; private byte[] tmpBuffer; /* * 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; // shared cryptographic objects private RandomData randomData; private KeyAgreement keyAgreement; private Signature sigECDSA; private Cipher aes128; /********************************************* * BIP32 Hierarchical Deterministic Wallet * *********************************************/ // Secure Memory & Object Manager with no access from outside (used internally for storing BIP32 objects) private MemoryManager bip32_mem; private ObjectManager bip32_om; // seed derivation private static final byte[] BITCOIN_SEED = {'B','i','t','c','o','i','n',' ','s','e','e','d'}; private static final byte[] BITCOIN_SEED2 = {'B','i','t','c','o','i','n',' ','s','e','e','d','2'}; private static final byte MAX_BIP32_DEPTH = 10; // max depth in extended key from master (m/i is depth 1) // BIP32_object= [ hash(address) (4b) | extended_key (32b) | chain_code (32b) | compression_byte(1b)] // recvBuffer=[ parent_chain_code (32b) | 0x00 | parent_key (32b) | hash(address) (32b) | current_extended_key(32b) | current_chain_code(32b) ] // hash(address)= [ index(4b) | unused (16b) | ANTICOLLISIONHASHTMP(4b)| crc (4b) | ANTICOLLISIONHASH(4b)] private static final short BIP32_KEY_SIZE= 32; // size of extended key and chain code is 256 bits private static final short BIP32_ANTICOLLISION_LENGTH=4; // max 12 bytes so that index+crc + two hashes fit in 32 bits private static final short BIP32_OBJECT_SIZE= (short)(2*BIP32_KEY_SIZE+BIP32_ANTICOLLISION_LENGTH+1); // offset in working buffer private static final short BIP32_OFFSET_PARENT_CHAINCODE=0; private static final short BIP32_OFFSET_PARENT_SEPARATOR=BIP32_KEY_SIZE; private static final short BIP32_OFFSET_PARENT_KEY=BIP32_KEY_SIZE+1; private static final short BIP32_OFFSET_INDEX= (short)(2*BIP32_KEY_SIZE+1); // offset in recvBuffer private static final short BIP32_OFFSET_COLLISIONHASHTMP= (short)(BIP32_OFFSET_INDEX+BIP32_KEY_SIZE-2*BIP32_ANTICOLLISION_LENGTH-4); private static final short BIP32_OFFSET_CRC= (short)(BIP32_OFFSET_INDEX+BIP32_KEY_SIZE-BIP32_ANTICOLLISION_LENGTH-4); private static final short BIP32_OFFSET_COLLISIONHASH= (short)(BIP32_OFFSET_INDEX+BIP32_KEY_SIZE-BIP32_ANTICOLLISION_LENGTH); private static final short BIP32_OFFSET_CHILD_KEY= (short)(BIP32_OFFSET_INDEX+BIP32_KEY_SIZE); private static final short BIP32_OFFSET_CHILD_CHAINCODE= (short)(BIP32_OFFSET_CHILD_KEY+BIP32_KEY_SIZE); private static final short BIP32_OFFSET_PUB= (short)(BIP32_OFFSET_CHILD_CHAINCODE+BIP32_KEY_SIZE); private static final short BIP32_OFFSET_PUBX= (short)(BIP32_OFFSET_PUB+1); private static final short BIP32_OFFSET_PUBY= (short)(BIP32_OFFSET_PUBX+BIP32_KEY_SIZE); private static final short BIP32_OFFSET_PATH= (short)(BIP32_OFFSET_PUBY+BIP32_KEY_SIZE); private static final short BIP32_OFFSET_END= (short)(BIP32_OFFSET_PATH+4*MAX_BIP32_DEPTH); private static final short BIP32_OFFSET_SIG= (short)(ISO7816.OFFSET_CDATA+4*MAX_BIP32_DEPTH); // bip32 keys private boolean bip32_seeded= false; private byte[] bip32_masterACL; // define right access for Write,Read, and Use private byte[] bip32_extendedACL; // define right access for Write,Read, and Use private byte bip32_master_compbyte; // compression byte for master key private AESKey bip32_masterkey; private AESKey bip32_masterchaincode; private AESKey bip32_encryptkey; // used to encrypt sensitive data in object private ECPrivateKey bip32_extendedkey; // object storing last extended key used private ECPrivateKey bip32_authentikey; // key used to authenticate data private ECPublicKey bip32_pubkey; /********************************************* * Other data instances * *********************************************/ // Message signing private static final byte[] BITCOIN_SIGNED_MESSAGE_HEADER = {0x18,'B','i','t','c','o','i','n',' ','S','i','g','n','e','d',' ','M','e','s','s','a','g','e',':','\n'}; //"Bitcoin Signed Message:\n"; private MessageDigest sha256; private boolean sign_flag= false; // transaction signing private byte[] transactionData; private static final byte OFFSET_TRANSACTION_HASH=0; private static final byte OFFSET_TRANSACTION_AMOUNT=OFFSET_TRANSACTION_HASH+32; private static final byte OFFSET_TRANSACTION_TOTAL=OFFSET_TRANSACTION_AMOUNT+8; private static final byte OFFSET_TRANSACTION_LIMIT=OFFSET_TRANSACTION_TOTAL+8; private static final byte OFFSET_TRANSACTION_HMACKEY=OFFSET_TRANSACTION_LIMIT+8; private static final byte OFFSET_TRANSACTION_SIZE=OFFSET_TRANSACTION_HMACKEY+20; private static final short HMAC_CHALRESP_2FA=(short)0x8000; // additional options private short option_flags; /**************************************** * Methods * ****************************************/ private CardEdge(byte[] bArray, short bOffset, byte bLength) { // FIXED: something should be done already here, not only with setup APDU /* If init pin code does not satisfy policies, internal error */ if (!CheckPINPolicy(PIN_INIT_VALUE, (short) 0, (byte) PIN_INIT_VALUE.length)) ISOException.throwIt(SW_INTERNAL_ERROR); ublk_pins = new OwnerPIN[MAX_NUM_PINS]; pins = new OwnerPIN[MAX_NUM_PINS]; // DONE: pass in starting PIN setting with instantiation /* Setting initial PIN n.0 value */ pins[0] = new OwnerPIN((byte) 3, (byte) PIN_INIT_VALUE.length); pins[0].update(PIN_INIT_VALUE, (short) 0, (byte) PIN_INIT_VALUE.length); // debug register(); } // end of constructor public static void install(byte[] bArray, short bOffset, byte bLength) { CardEdge wal = new CardEdge(bArray, bOffset, bLength); // // debug this part not useful? // /* 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_SETUP_NOT_DONE); 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_GEN_KEYSYM: GenerateSymmetricKey(apdu, buffer); break; case INS_IMPORT_KEY: ImportKey(apdu, buffer); break; case INS_GET_PUBLIC_FROM_PRIVATE: getPublicKeyFromPrivate(apdu, buffer); break; // case INS_EXPORT_KEY: // ExportKey(apdu, buffer); // break; case INS_COMPUTE_CRYPT: ComputeCrypt(apdu, buffer); break; case INS_COMPUTE_SIGN: ComputeSign(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_SIZE_OBJ: GetObjectSize(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; case INS_COMPUTE_SHA512: ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); // only for debug purpose //computeSha512(apdu, buffer); break; case INS_COMPUTE_HMAC: //ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); // only for debug purpose computeHmac(apdu, buffer); break; case INS_BIP32_IMPORT_SEED: importBIP32Seed(apdu, buffer); break; case INS_BIP32_GET_AUTHENTIKEY: getBIP32AuthentiKey(apdu, buffer); break; case INS_BIP32_GET_EXTENDED_KEY: getBIP32ExtendedKey(apdu, buffer); break; case INS_SIGN_MESSAGE: signMessage(apdu, buffer); break; case INS_SIGN_SHORT_MESSAGE: signShortMessage(apdu, buffer); break; case INS_SIGN_TRANSACTION: SignTransaction(apdu, buffer); break; case INS_BIP32_SET_EXTENDED_KEY: ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); // only for debug purpose //setBIP32ExtendedKey(apdu, buffer); break; case INS_PARSE_TRANSACTION: ParseTransaction(apdu, buffer); break; // case INS_TEST_SHA1: // byte p1= buffer[ISO7816.OFFSET_P1]; // switch(p1){ // // add_carry // case 0x00: // Sha512.test_add_carry(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8)); // break; // case 0x01: // Sha512.test_add_carry_fast(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8)); // break; // case 0x02: // Sha512.test_add_carry_fast2(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8)); // break; // case 0x03: // Sha512.test_add_carry_fast3(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8)); // break; // // Ch // case 0x04: // Sha512.test_Ch(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8), buffer, (short)(ISO7816.OFFSET_CDATA+16), buffer, (short)(ISO7816.OFFSET_CDATA+24)); // break; // case 0x05: // Sha512.test_Ch_fast(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8), buffer, (short)(ISO7816.OFFSET_CDATA+16), buffer, (short)(ISO7816.OFFSET_CDATA+24)); // break; // // Maj // case 0x06: // Sha512.test_Maj(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8), buffer, (short)(ISO7816.OFFSET_CDATA+16), buffer, (short)(ISO7816.OFFSET_CDATA+24)); // break; // case 0x07: // Sha512.test_Maj_fast(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8), buffer, (short)(ISO7816.OFFSET_CDATA+16), buffer, (short)(ISO7816.OFFSET_CDATA+24)); // break; // // E0-E1-Sig0-Sig1 // case 0x10: // Sha512.test_E0_opt(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8)); // break; // case 0x11: // Sha512.test_E0_fast(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8)); // break; // case 0x12: // Sha512.test_E1_opt(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8)); // break; // case 0x13: // Sha512.test_E1_fast(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8)); // break; // case 0x14: // Sha512.test_Sig0_opt(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8)); // break; // case 0x15: // Sha512.test_Sig0_fast(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8)); // break; // case 0x16: // Sha512.test_Sig1_opt(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8)); // break; // case 0x17: // Sha512.test_Sig1_fast(buffer, (short)ISO7816.OFFSET_CDATA, buffer, (short)(ISO7816.OFFSET_CDATA+8)); // break; // case 0x20: // Sha512.test_updateW(buffer, ISO7816.OFFSET_CDATA, buffer[ISO7816.OFFSET_P2]); // break; // case 0x21: // Sha512.test_updateW_fast(buffer, ISO7816.OFFSET_CDATA, buffer[ISO7816.OFFSET_P2]); // break; // // } // apdu.setOutgoingAndSend((short) 0, (short)(128)); // break; default: ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); } } // end of process method /** * Setup APDU - initialize the applet and reserve memory * This is done only once during the lifetime of the applet * * ins: INS_SETUP (0x2A) * p1: 0x00 * p2: 0x00 * data: [default_pin_length(1b) | default_pin | * pin_tries0(1b) | ublk_tries0(1b) | pin0_length(1b) | pin0 | ublk0_length(1b) | ublk0 | * pin_tries1(1b) | ublk_tries1(1b) | pin1_length(1b) | pin1 | ublk1_length(1b) | ublk1 | * secmemsize(2b) | memsize(2b) | ACL(3b) | * option_flags(2b) | * hmacksha1_key(20b) | amount_limit(8b)] * where: * default_pin: {0x4D, 0x75, 0x73, 0x63, 0x6C, 0x65, 0x30, 0x30}; * pin_tries: max number of PIN try allowed before the corresponding PIN is blocked * ublk_tries: max number of UBLK(unblock) try allowed before the PUK is blocked * secmemsize: number of bytes reserved for internal memory (storage of Bip32 objects) * memsize: number of bytes reserved for memory with external access * ACL: creation rights for objects - Key - PIN * option_flags: flags to define up to 16 additional options: * bit15 set: second factor authentication using hmac-sha1 challenge-response (v0.2-0.1) * hmacsha1_key: 20-byte hmac key used for transaction authorization * amount_limit: max amount (in satoshis) allowed without confirmation (this includes change value) * * return: none */ 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++]; bytesLeft--; OwnerPIN pin = pins[0]; if (!CheckPINPolicy(buffer, base, numBytes)) ISOException.throwIt(SW_INVALID_PARAMETER); if (pin.getTriesRemaining() == (byte) 0x00) ISOException.throwIt(SW_IDENTITY_BLOCKED); if (!pin.check(buffer, base, numBytes)) ISOException.throwIt(SW_AUTH_FAILED); base += numBytes; bytesLeft-=numBytes; byte pin_tries = buffer[base++]; byte ublk_tries = buffer[base++]; numBytes = buffer[base++]; bytesLeft-=3; 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; bytesLeft-=numBytes; numBytes = buffer[base++]; bytesLeft--; 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; bytesLeft-=numBytes; pin_tries = buffer[base++]; ublk_tries = buffer[base++]; numBytes = buffer[base++]; bytesLeft-=3; 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; bytesLeft-=numBytes; numBytes = buffer[base++]; bytesLeft--; 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; bytesLeft-=numBytes; short secmem_size= Util.getShort(buffer, base); base += (short) 2; short mem_size = Util.getShort(buffer, base); base += (short) 2; bytesLeft-=4; create_object_ACL = buffer[base++]; create_key_ACL = buffer[base++]; create_pin_ACL = buffer[base++]; bytesLeft-=3; mem = new MemoryManager((short) mem_size); om = new ObjectManager(mem); bip32_mem = new MemoryManager((short) secmem_size); bip32_om = new ObjectManager(bip32_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 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]; } // temporary buffer try { tmpBuffer = JCSystem.makeTransientByteArray((short) TMP_BUFFER_SIZE, JCSystem.CLEAR_ON_DESELECT); } catch (SystemException e) { tmpBuffer = new byte[TMP_BUFFER_SIZE]; } // shared cryptographic objects randomData = null; // Will be created on demand when needed keyAgreement = KeyAgreement.getInstance(ALG_EC_SVDP_DH_PLAIN, false); sigECDSA= Signature.getInstance(ALG_ECDSA_SHA_256, false); aes128= Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, false); // HD wallet Sha512.init(); HmacSha512.init(tmpBuffer); EccComputation.init(tmpBuffer); // bip32 material bip32_seeded= false; bip32_master_compbyte=0x04; bip32_masterkey= (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false); bip32_masterchaincode= (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false); bip32_encryptkey= (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); bip32_masterACL= new byte[KEY_ACL_SIZE]; bip32_extendedACL= new byte[KEY_ACL_SIZE]; // object containing the current extended key bip32_extendedkey= (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, LENGTH_EC_FP_256, false); Secp256k1.setCommonCurveParameters(bip32_extendedkey); // key used to authenticate sensitive data from applet bip32_authentikey= (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, LENGTH_EC_FP_256, false); Secp256k1.setCommonCurveParameters(bip32_authentikey); // key used to recover public key from private bip32_pubkey= (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, LENGTH_EC_FP_256, false); Secp256k1.setCommonCurveParameters(bip32_pubkey); // message signing sha256= MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false); // Transaction signing Transaction.init(); HmacSha160.init(tmpBuffer); transactionData= new byte[OFFSET_TRANSACTION_SIZE]; // parse options option_flags=0; if (bytesLeft>=2){ option_flags = Util.getShort(buffer, base); base+=(short)2; bytesLeft-=(short)2; // transaction confirmation based on hmacsha160 if ((option_flags & HMAC_CHALRESP_2FA)==HMAC_CHALRESP_2FA){ Util.arrayCopyNonAtomic(buffer, base, transactionData, OFFSET_TRANSACTION_HMACKEY, (short)20); base+=(short)20; bytesLeft-=(short)20; Util.arrayCopyNonAtomic(buffer, base, transactionData, OFFSET_TRANSACTION_LIMIT, (short)8); base+=(short)8; bytesLeft-=(short)8; } } 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) { try { signatures[key_nb] = Signature.getInstance(alg_id, false); } catch (Exception e) { ISOException.throwIt(((CardRuntimeException) e).getReason()); } } 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) { 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(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() != key_type)) ISOException.throwIt(SW_OPERATION_NOT_ALLOWED); } return keys[key_nb]; } /** Check from key_nb key ACL if an operation can be done */ boolean authorizeKeyOp(byte key_nb, byte op) { short acl_offset = (short) (key_nb * KEY_ACL_SIZE+ op); short required_ids = Util.getShort(keyACLs, acl_offset); return ((required_ids != (short) 0xFFFF) && ((short) (required_ids & logged_ids) == required_ids)); } /** Check from ACL if the corresponding key can be overwritten */ boolean authorizeKeyOp(byte[] ACL, byte op) { short required_ids = Util.getShort(ACL, (short)op); 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]; //to do: avoid allocation outside setup? 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]; //to do: avoid allocation outside setup? // 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 * ****************************************/ /** * This function performs encryption/decryption on provided data, using a key on * the card. It also allows proper initialization of the card cipher with custom data, if * required by the application. Usually, this function is called 1 time for cipher * initialization (CIPHER_INIT), 0 or more times for intermediate data processing * (CIPHER_UPDATE) and 1 time for last data processing (CIPHER_FINAL). * * ins: 0x36 * p1: key number (0x00-0x0F) * p2: operation (Init-Update-Final) * data(Init): [cipher_mode(1b)|cipher_direction(1b)|cipher_location(1b)|option_data_size(1b)|option_data] * data(Update/Final): [data_location(1b)|data_size(2b)|data] * * return(init): [IV(blocksize)] or none (ECB mode or RSA) * return(Update/Final): [data_size(2b)|processed_data] */ private void ComputeCrypt(APDU apdu, byte[] apduBuffer) { /* Buffer pointer */ byte[] buffer = apduBuffer; //extended length 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 (!authorizeKeyOp(key_nb,ACL_USE)) ISOException.throwIt(SW_UNAUTHORIZED); byte op = buffer[ISO7816.OFFSET_P2]; Key key = keys[key_nb]; byte ciph_mode; byte ciph_dir; byte ciph_alg_id=0; byte data_location; short size; Cipher ciph; switch (op) { case OP_INIT: if (bytesLeft < 3) ISOException.throwIt(SW_INVALID_PARAMETER); ciph_mode = buffer[dataOffset]; ciph_dir = buffer[(short) (dataOffset + 1)]; if (ciph_dir!=Cipher.MODE_ENCRYPT && ciph_dir!=Cipher.MODE_DECRYPT ) ISOException.throwIt(SW_INVALID_PARAMETER); data_location = buffer[(short) (dataOffset + 2)]; if (data_location!=DL_APDU) // only support data in apdu ISOException.throwIt(SW_INVALID_PARAMETER); dataOffset+= 3; bytesLeft-= 3; if (bytesLeft < 2) ISOException.throwIt(SW_INVALID_PARAMETER); size = Util.getShort(buffer, dataOffset); if (bytesLeft < (short) (2 + size)) ISOException.throwIt(SW_INVALID_PARAMETER); short IVlength=0; switch (key.getType()) { case KeyBuilder.TYPE_RSA_PUBLIC: case KeyBuilder.TYPE_RSA_PRIVATE: case KeyBuilder.TYPE_RSA_CRT_PRIVATE: if (ciph_mode == Cipher.ALG_RSA_NOPAD) ciph_alg_id = Cipher.ALG_RSA_NOPAD; else if (ciph_mode == Cipher.ALG_RSA_PKCS1) ciph_alg_id = Cipher.ALG_RSA_PKCS1; else ISOException.throwIt(SW_INVALID_PARAMETER); break; case KeyBuilder.TYPE_DES: if (ciph_mode == Cipher.ALG_DES_CBC_NOPAD){ IVlength= (short)(8); ciph_alg_id = Cipher.ALG_DES_CBC_NOPAD; } else if (ciph_mode == Cipher.ALG_DES_ECB_NOPAD) ciph_alg_id = Cipher.ALG_DES_ECB_NOPAD; else ISOException.throwIt(SW_INVALID_PARAMETER); break; case KeyBuilder.TYPE_AES: if (ciph_mode == Cipher.ALG_AES_BLOCK_128_CBC_NOPAD){ IVlength= (short)(16); ciph_alg_id = Cipher.ALG_AES_BLOCK_128_CBC_NOPAD; } else if (ciph_mode == Cipher.ALG_AES_BLOCK_128_ECB_NOPAD) ciph_alg_id = Cipher.ALG_AES_BLOCK_128_ECB_NOPAD; else ISOException.throwIt(SW_INVALID_PARAMETER); break; default: ISOException.throwIt(SW_INTERNAL_ERROR); return; // Compiler warning (ciph_alg_id unset) } // init cipher // encrypt // IV=0 (rsa, ecb) // IV!=0 generateOnCard (des/aes cbc) // decrypt // IV=0 (rsa, ecb) // IV!=0 getFromBuffer (des/aes cbc) ciph = getCipher(key_nb, ciph_alg_id); if (IVlength == (short) 0) ciph.init(key, (ciph_dir == Cipher.MODE_ENCRYPT) ? Cipher.MODE_ENCRYPT : Cipher.MODE_DECRYPT); else if (ciph_dir== Cipher.MODE_ENCRYPT){ if (randomData == null) randomData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); randomData.generateData(buffer,(short)0,IVlength); ciph.init(key, Cipher.MODE_ENCRYPT, buffer, (short)0, IVlength); apdu.setOutgoingAndSend((short) 0, IVlength); } else if (ciph_dir== Cipher.MODE_DECRYPT){ ciph.init(key, Cipher.MODE_DECRYPT, buffer, (short) (dataOffset + 2), IVlength); // ciph.init(key, (ciph_dir == Cipher.MODE_ENCRYPT) ? Cipher.MODE_ENCRYPT : Cipher.MODE_DECRYPT, buffer, (short) (dataOffset + 2), size); } ciph_dirs[key_nb] = ciph_dir; break; case OP_PROCESS: case OP_FINALIZE: ciph_dir = ciph_dirs[key_nb]; if (ciph_dir!=Cipher.MODE_ENCRYPT && ciph_dir!=Cipher.MODE_DECRYPT) // Internal error because it should have been checked on INIT ISOException.throwIt(SW_INTERNAL_ERROR); 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]; if(data_location!=DL_APDU) ISOException.throwIt(SW_INVALID_PARAMETER); dataOffset+=1; bytesLeft-=1; if (bytesLeft < 2) ISOException.throwIt(SW_INVALID_PARAMETER); size = Util.getShort(buffer, dataOffset); if (bytesLeft < (short) (2 + size)) ISOException.throwIt(SW_INVALID_PARAMETER); short sizeout=0; if (op == OP_PROCESS) sizeout=ciph.update(buffer, (short) (dataOffset + 2), size, buffer, (short) 2); else /* op == OP_FINAL */ sizeout=ciph.doFinal(buffer, (short) (dataOffset + 2), size, buffer, (short) 2); // Also copies the Short size information Util.setShort(buffer,(short)0, sizeout); sendData(apdu, buffer, (short) 0, (short) (sizeout + 2)); break; default: ISOException.throwIt(SW_INCORRECT_P2); } // switch(op) } /** * This function performs signature/verification on provided data, using a key on * the card. It also allows proper initialization of the card cipher with custom data, if * required by the application. Usually, this function is called 1 time for cipher * initialization (CIPHER_INIT), 0 or more times for intermediate data processing * (CIPHER_UPDATE) and 1 time for last data processing (CIPHER_FINAL). * * ins: 0x37 * p1: key number (0x00-0x0F) * p2: operation (Init-Update-Final) * data(Init): [cipher_mode(1b)|cipher_direction(1b)|cipher_location(1b)|option_data_size(1b)|option_data] * data(Update/Final): [data_location(1b)|data_size(2b)|data] * * return(init/update): none * return(final-signature): signature in ASN1 format * return(final-verification): none (throws exception in case of signature) */ private void ComputeSign(APDU apdu, byte[] apduBuffer) { /* Buffer pointer */ byte[] buffer = apduBuffer; //extended length 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 (!authorizeKeyOp(key_nb,ACL_USE)) ISOException.throwIt(SW_UNAUTHORIZED); byte op = buffer[ISO7816.OFFSET_P2]; Key key = keys[key_nb]; byte ciph_mode; byte ciph_dir; byte ciph_alg_id=0; byte data_location; short size; Signature sign; switch (op) { case OP_INIT: if (bytesLeft < 3) ISOException.throwIt(SW_INVALID_PARAMETER); ciph_mode = buffer[dataOffset]; ciph_dir = buffer[(short) (dataOffset + 1)]; if (ciph_dir!=Signature.MODE_SIGN && ciph_dir!=Signature.MODE_VERIFY ) ISOException.throwIt(SW_INVALID_PARAMETER); data_location = buffer[(short) (dataOffset + 2)]; if (data_location!=DL_APDU) ISOException.throwIt(SW_INVALID_PARAMETER); dataOffset+=3; bytesLeft-=3; if (bytesLeft < 2) ISOException.throwIt(SW_INVALID_PARAMETER); size = Util.getShort(buffer, dataOffset); if (bytesLeft < (short) (2 + size)) ISOException.throwIt(SW_INVALID_PARAMETER); switch (key.getType()) { case KeyBuilder.TYPE_RSA_PUBLIC: case KeyBuilder.TYPE_RSA_PRIVATE: case KeyBuilder.TYPE_RSA_CRT_PRIVATE: // FIXED if (ciph_mode==Signature.ALG_RSA_MD5_PKCS1) // FIXED ciph_alg_id = Signature.ALG_RSA_MD5_PKCS1; // ALG_RSA_SHA_PKCS1 instead of ALG_RSA_MD5_PKCS1? else // FIXED ISOException.throwIt(SW_UNSUPPORTED_FEATURE); break; // For elliptic curves, use signTransaction and signMessage instead // case KeyBuilder.TYPE_EC_FP_PUBLIC: // case KeyBuilder.TYPE_EC_FP_PRIVATE: // if (ciph_mode == ALG_ECDSA_SHA_256) // ciph_alg_id = ALG_ECDSA_SHA_256; //Signature.ALG_ECDSA_SHA_256; //=33 // else if (ciph_mode == Signature.ALG_ECDSA_SHA) // ciph_alg_id = Signature.ALG_ECDSA_SHA ; // else // ISOException.throwIt(SW_INVALID_PARAMETER); // break; default: ISOException.throwIt(SW_INCORRECT_ALG); return; } sign = getSignature(key_nb, ciph_alg_id); if (size == (short) 0) sign.init(key, (ciph_dir == Signature.MODE_SIGN) ? Signature.MODE_SIGN : Signature.MODE_VERIFY); else sign.init(key, (ciph_dir == Signature.MODE_SIGN) ? Signature.MODE_SIGN : Signature.MODE_VERIFY, buffer, (short) (dataOffset + 2), size); ciph_dirs[key_nb] = ciph_dir; break; // OP_INIT case OP_PROCESS: case OP_FINALIZE: ciph_dir = ciph_dirs[key_nb]; if (ciph_dir!=Signature.MODE_SIGN && ciph_dir!=Signature.MODE_VERIFY ) // Internal error because it should have been checked on INIT ISOException.throwIt(SW_INTERNAL_ERROR); 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]; if (data_location!=DL_APDU) ISOException.throwIt(SW_INVALID_PARAMETER); dataOffset+=1; bytesLeft-=1; if (bytesLeft < 2){ ISOException.throwIt(SW_INVALID_PARAMETER);} size = Util.getShort(buffer, dataOffset); if (bytesLeft < (short) (2 + size)){ ISOException.throwIt(SW_INVALID_PARAMETER);} if (op == OP_PROCESS){ sign.update(buffer, (short) (dataOffset + 2), size); }else { // OP_FINALIZE if (ciph_dir == Signature.MODE_SIGN) { short sign_size = sign.sign(buffer, (short) (dataOffset + 2), size, buffer, (short) 0); 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); //ISOException.throwIt(Util.makeShort((byte)(sign_size&0xFF), (byte)(sign.getLength()))); sendData(apdu, buffer, (short)0, sign_size); } else { // ciph_dir == CD_VERIFY if (bytesLeft < (short) (2 + size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); short sign_size = Util.getShort(buffer, (short) (dataOffset + 2 + size)); if (bytesLeft < (short) (2 + size + 2 + sign_size)) ISOException.throwIt(SW_INVALID_PARAMETER); //if (sign_size != sign.getLength()) //commented for debug: size mismatch for ECDSA sig: sign.getLength()==0x38, sign_size=0x36 //ISOException.throwIt(SW_INVALID_PARAMETER); if (!sign.verify(buffer, (short) (dataOffset + 2), size, buffer, (short) (dataOffset + 2 + size + 2), sign_size)) ISOException.throwIt(SW_SIGNATURE_INVALID); } } break; default: ISOException.throwIt(SW_INCORRECT_P2); } // switch(op) } /** * This function generates a key pair using the card's on board key generation * process. The key number (or numbers if a key pair is being generated), algorithm * type, and algorithm parameters are specified by arguments P1 and P2 and by * provided DATA. * * ins: 0x30 * p1: private key number (0x00-0x0F) * p2: public key number (0x00-0x0F) * data: [Key Generation Parameters] * return: none */ 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 KeyPair.ALG_RSA: case KeyPair.ALG_RSA_CRT: GenerateKeyPairRSA(buffer); break; case KeyPair.ALG_EC_FP: //GenerateKeyPairECFP(buffer); GeneratePrivateKeyECFP(buffer); break; default: ISOException.throwIt(SW_INCORRECT_ALG); } } // Data has already been received 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, KeyBuilder.TYPE_RSA_PUBLIC, key_size); RSAPrivateKey prv_key = (RSAPrivateKey) getKey(prv_key_nb, alg_id == KeyPair.ALG_RSA ? KeyBuilder.TYPE_RSA_PRIVATE : KeyBuilder.TYPE_RSA_CRT_PRIVATE, key_size); /* If we're going to overwrite a keyPair's contents, check ACL */ if (pub_key.isInitialized() && !authorizeKeyOp(pub_key_nb,ACL_WRITE)) ISOException.throwIt(SW_UNAUTHORIZED); if (prv_key.isInitialized() && !authorizeKeyOp(prv_key_nb,ACL_WRITE)) 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(); } // // Bitcoin // // not supported by current chips? // // Data has already been received // private void GenerateKeyPairECFP(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); // short key_size = Util.getShort(buffer, OFFSET_GENKEY_SIZE); // byte options = buffer[OFFSET_GENKEY_OPTIONS]; // ECPublicKey pub_key = (ECPublicKey) getKey(pub_key_nb, KeyBuilder.TYPE_EC_FP_PUBLIC, key_size); // ECPrivateKey prv_key = (ECPrivateKey) getKey(prv_key_nb, KeyBuilder.TYPE_EC_FP_PRIVATE, key_size); // /* If we're going to overwrite a keyPair's contents, check ACL */ // if (pub_key.isInitialized() && !authorizeKeyOp(pub_key_nb,ACL_WRITE)) // ISOException.throwIt(SW_UNAUTHORIZED); // if (prv_key.isInitialized() && !authorizeKeyOp(prv_key_nb,ACL_WRITE)) // 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(); // if (prv_key.isInitialized()) // prv_key.clearKey(); // break; // case OPT_EC_SECP256k1: // // Bitcoin uses 256-bit keysize! // if (key_size!=256) // ISOException.throwIt(SW_INVALID_PARAMETER); // // 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(); // if (prv_key.isInitialized()) // prv_key.clearKey(); // // PINCOIN default is secp256k1 (over Fp) // pub_key.setFieldFP( SECP256K1_P, (short)0, (short)SECP256K1_P.length); // prv_key.setFieldFP( SECP256K1_P, (short)0, (short)SECP256K1_P.length); // pub_key.setA( SECP256K1_a, (short)0, (short)SECP256K1_a.length); // prv_key.setA( SECP256K1_a, (short)0, (short)SECP256K1_a.length); // pub_key.setB( SECP256K1_b, (short)0, (short)SECP256K1_b.length); // prv_key.setB( SECP256K1_b, (short)0, (short)SECP256K1_b.length); // pub_key.setG( SECP256K1_G, (short)0, (short)SECP256K1_G.length); // prv_key.setG( SECP256K1_G, (short)0, (short)SECP256K1_G.length); // pub_key.setR( SECP256K1_R, (short)0, (short)SECP256K1_R.length); // prv_key.setR( SECP256K1_R, (short)0, (short)SECP256K1_R.length); // pub_key.setK( SECP256K1_K); // prv_key.setK( SECP256K1_K); // 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); // } // } // Bitcoin // Generate only a random private key // the corresponding public key can be recovered externally using getPublicKeyFromPrivate() private void GeneratePrivateKeyECFP(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); short key_size = Util.getShort(buffer, OFFSET_GENKEY_SIZE); byte options = buffer[OFFSET_GENKEY_OPTIONS]; ECPrivateKey prv_key = (ECPrivateKey) getKey(prv_key_nb, KeyBuilder.TYPE_EC_FP_PRIVATE, key_size); /* If we're going to overwrite a keyPair's contents, check ACL */ if (prv_key.isInitialized() && !authorizeKeyOp(prv_key_nb,ACL_WRITE)) 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); if (randomData == null) randomData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); 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 (prv_key.isInitialized()) prv_key.clearKey(); break; case OPT_EC_SECP256k1: // Bitcoin uses 256-bit keysize! if (key_size!=256) ISOException.throwIt(SW_INVALID_PARAMETER); // Satochip default is secp256k1 (over Fp) Secp256k1.setCommonCurveParameters(prv_key); // prv_key.setFieldFP( SECP256K1_P, (short)0, (short)SECP256K1_P.length); // prv_key.setA( SECP256K1_a, (short)0, (short)SECP256K1_a.length); // prv_key.setB( SECP256K1_b, (short)0, (short)SECP256K1_b.length); // prv_key.setG( SECP256K1_G, (short)0, (short)SECP256K1_G.length); // prv_key.setR( SECP256K1_R, (short)0, (short)SECP256K1_R.length); // prv_key.setK( SECP256K1_K); // Set secret value from random randomData.generateData(recvBuffer,(short)0,(short)(key_size/8)); prv_key.setS(recvBuffer, (short)0, (short)(key_size/8)); Util.arrayFillNonAtomic(recvBuffer, (short)0, (short)(key_size/8), (byte)0); break; default: ISOException.throwIt(SW_INVALID_PARAMETER); } } /** * This function generates a symmetric key using the card's on board key generation * process. The key number, key type, and key size (in bits) are specified * by arguments P1 and P2 and by provided DATA. * * ins: 0x31 * p1: symmetric key number (0x00-0x0F) * p2: 0x00 * data: [key_type(1) | key_size(2) | key_ACL(6)] * return: none */ private void GenerateSymmetricKey(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 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() && !authorizeKeyOp(key_nb,ACL_WRITE)) ISOException.throwIt(SW_UNAUTHORIZED); /*** Start reading key blob header***/ // blob header= [ key_type(1) | key_size(2) | key_ACL(6)] // Check entire blob header if (bytesLeft < 9) ISOException.throwIt(SW_INVALID_PARAMETER); short dataOffset= ISO7816.OFFSET_CDATA; byte key_type = buffer[dataOffset]; dataOffset++; // Skip Key Type bytesLeft--; short key_size = Util.getShort(buffer, dataOffset); dataOffset += (short) 2; // Skip Key Size bytesLeft -= (short) 2; Util.arrayCopy(buffer, dataOffset, keyACLs, (short) (key_nb * KEY_ACL_SIZE), KEY_ACL_SIZE); dataOffset += (short) 6; // Skip ACL bytesLeft -= (short) 6; if (randomData == null) randomData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); switch (key_type) { case KeyBuilder.TYPE_AES: AESKey aes_key = (AESKey) getKey(key_nb, key_type, key_size); randomData.generateData(recvBuffer,(short)0,(short)(key_size/8)); aes_key.setKey(recvBuffer, (short)0); break; case KeyBuilder.TYPE_DES: DESKey des_key = (DESKey) getKey(key_nb, key_type, key_size); randomData.generateData(recvBuffer,(short)0,(short)(key_size/8)); des_key.setKey(recvBuffer, (short)0); break; default: ISOException.throwIt(SW_INCORRECT_ALG); } // clear recvBuffer Util.arrayFillNonAtomic(recvBuffer, (short)0, (short)(key_size/8), (byte)0); } /** * This function allows the import of a key into the card. * The exact key blob contents depend on the key�s algorithm, type and actual * import parameters. The key's number, algorithm type, and parameters are * specified by arguments P1, P2 and DATA. * * ins: 0x32 * p1: private key number (0x00-0x0F) * p2: 0x00 * data: [key_encoding(1) | key_type(1) | key_size(2) | key_ACL(6) | key_blob] * return: none */ private void ImportKey(APDU apdu, byte[] apduBuffer) { if (apduBuffer[ISO7816.OFFSET_P2] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P2); /* Buffer pointer */ byte[] buffer = apduBuffer; //extended length 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)) 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() && !authorizeKeyOp(key_nb,ACL_WRITE)) ISOException.throwIt(SW_UNAUTHORIZED); /*** Start reading key blob header***/ // blob header= [ key_encoding(1) | key_type(1) | key_size(2) | key_ACL(6)] // Check entire blob header if (bytesLeft < 4) ISOException.throwIt(SW_INVALID_PARAMETER); // Check Blob Encoding - TODO: Encrypted key blob ? if (buffer[dataOffset] != BLOB_ENC_PLAIN) ISOException.throwIt(SW_UNSUPPORTED_FEATURE); dataOffset++; // Skip Blob Encoding bytesLeft--; byte key_type = buffer[dataOffset]; dataOffset++; // Skip Key Type bytesLeft--; short key_size = Util.getShort(buffer, dataOffset); dataOffset += (short) 2; // Skip Key Size bytesLeft -= (short) 2; Util.arrayCopy(buffer, dataOffset, keyACLs, (short) (key_nb * KEY_ACL_SIZE), KEY_ACL_SIZE); dataOffset += (short) 6; // Skip ACL bytesLeft -= (short) 6; /*** Start reading key blob ***/ short blob_size; switch (key_type) { case KeyBuilder.TYPE_EC_FP_PUBLIC: // BITCOIN // key_blob=[blob_size(2) | pubkey_blob(1+32+32)] if (key_size != 256) ISOException.throwIt(key_size); ECPublicKey ec_pub_key = (ECPublicKey) getKey(key_nb, key_type, key_size); if (bytesLeft < 2) ISOException.throwIt(SW_INVALID_PARAMETER); blob_size = Util.getShort(buffer, dataOffset); if (blob_size != 65) //only uncompressed point ISOException.throwIt(blob_size); dataOffset += (short) 2; bytesLeft -= (short) 2; if (bytesLeft < (short) (blob_size)) ISOException.throwIt(SW_INVALID_PARAMETER); // others curves parameters are take by default as SECP256k1 // Satochip default is secp256k1 (over Fp) Secp256k1.setCommonCurveParameters(ec_pub_key); // ec_pub_key.setFieldFP( SECP256K1_P, (short)0, (short)SECP256K1_P.length); // ec_pub_key.setA( SECP256K1_a, (short)0, (short)SECP256K1_a.length); // ec_pub_key.setB( SECP256K1_b, (short)0, (short)SECP256K1_b.length); // ec_pub_key.setG( SECP256K1_G, (short)0, (short)SECP256K1_G.length); // ec_pub_key.setR( SECP256K1_R, (short)0, (short)SECP256K1_R.length); // ec_pub_key.setK( SECP256K1_K); // set public point ec_pub_key.setW(buffer, dataOffset, (short)(blob_size)); // https://javacard.kenai.com/javadocs/classic/javacard/security/ECPrivateKey.html // The plain text data format is big-endian and right-aligned (the least significant bit is the least significant bit of last byte) dataOffset += blob_size; bytesLeft -= blob_size; break; case KeyBuilder.TYPE_EC_FP_PRIVATE: // BITCOIN // key_blob=[blob_size(2) | privkey_blob(32)] if (key_size != 256) ISOException.throwIt(key_size); ECPrivateKey ec_prv_key = (ECPrivateKey) getKey(key_nb, key_type, key_size); if (bytesLeft < 2) ISOException.throwIt(SW_INVALID_PARAMETER); blob_size = Util.getShort(buffer, dataOffset); if (blob_size != 32) // only bitcoin ISOException.throwIt(blob_size); dataOffset += (short) 2; bytesLeft -= (short) 2; if (bytesLeft < (short) (blob_size)) ISOException.throwIt(SW_INVALID_PARAMETER); // curves parameters are take by default as SECP256k1 // Satochip default is secp256k1 (over Fp) Secp256k1.setCommonCurveParameters(ec_prv_key); // set from secret value ec_prv_key.setS(buffer, dataOffset, blob_size); dataOffset += blob_size; bytesLeft -= blob_size; break; case KeyBuilder.TYPE_RSA_PUBLIC: RSAPublicKey rsa_pub_key = (RSAPublicKey) getKey(key_nb, key_type, key_size); if (bytesLeft < 2) ISOException.throwIt(SW_INVALID_PARAMETER); blob_size = Util.getShort(buffer, dataOffset); dataOffset += (short) 2; // Skip Mod Size bytesLeft -= (short) 2; if (bytesLeft < (short) (blob_size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_pub_key.setModulus(buffer, dataOffset, blob_size); dataOffset += blob_size; // Skip Mod Value bytesLeft -= blob_size; // bytesLeft already checked in previous if () blob_size = Util.getShort(buffer, dataOffset); dataOffset += (short) 2; // Skip Exp Size bytesLeft -= (short) 2; if (bytesLeft < blob_size) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_pub_key.setExponent(buffer, dataOffset, blob_size); dataOffset += blob_size; // Skip Exp Value bytesLeft -= blob_size; break; case KeyBuilder.TYPE_RSA_PRIVATE: RSAPrivateKey rsa_prv_key = (RSAPrivateKey) getKey(key_nb, key_type, key_size); if (bytesLeft < 2) ISOException.throwIt(SW_INVALID_PARAMETER); blob_size = Util.getShort(buffer, dataOffset); dataOffset += (short) 2; // Skip Mod Size bytesLeft -= (short) 2; if (bytesLeft < (short) (blob_size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_prv_key.setModulus(buffer, dataOffset, blob_size); dataOffset += blob_size; // Skip Mod Value bytesLeft -= blob_size; // bytesLeft already checked in previous if () blob_size = Util.getShort(buffer, dataOffset); dataOffset += (short) 2; // Skip Exp Size bytesLeft -= (short) 2; if (bytesLeft < blob_size) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_prv_key.setExponent(buffer, dataOffset, blob_size); dataOffset += blob_size; // Skip Exp Value bytesLeft -= blob_size; break; case KeyBuilder.TYPE_RSA_CRT_PRIVATE: RSAPrivateCrtKey rsa_prv_key_crt = (RSAPrivateCrtKey) getKey(key_nb, key_type, key_size); if (bytesLeft < 2) ISOException.throwIt(SW_INVALID_PARAMETER); blob_size = Util.getShort(buffer, dataOffset); dataOffset += (short) 2; // Skip P Size bytesLeft -= (short) 2; if (bytesLeft < (short) (blob_size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_prv_key_crt.setP(buffer, dataOffset, blob_size); dataOffset += blob_size; // Skip P Value bytesLeft -= blob_size; // bytesLeft ok... blob_size = Util.getShort(buffer, dataOffset); dataOffset += (short) 2; // Skip Q Size bytesLeft -= (short) 2; if (bytesLeft < (short) (blob_size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_prv_key_crt.setQ(buffer, dataOffset, blob_size); dataOffset += blob_size; // Skip Q Value bytesLeft -= blob_size; // bytesLeft ok... blob_size = Util.getShort(buffer, dataOffset); dataOffset += (short) 2; // Skip PQ Size bytesLeft -= (short) 2; if (bytesLeft < (short) (blob_size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_prv_key_crt.setPQ(buffer, dataOffset, blob_size); dataOffset += blob_size; // Skip PQ Value bytesLeft -= blob_size; // bytesLeft ok... blob_size = Util.getShort(buffer, dataOffset); dataOffset += (short) 2; // Skip DP1 Size bytesLeft -= (short) 2; if (bytesLeft < (short) (blob_size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_prv_key_crt.setDP1(buffer, dataOffset, blob_size); dataOffset += blob_size; // Skip DP1 Value bytesLeft -= blob_size; // bytesLeft ok... blob_size = Util.getShort(buffer, dataOffset); dataOffset += (short) 2; // Skip DQ1 Size bytesLeft -= (short) 2; if (bytesLeft < blob_size) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_prv_key_crt.setDQ1(buffer, dataOffset, blob_size); dataOffset += blob_size; // Skip DQ1 Value bytesLeft -= blob_size; break; case KeyBuilder.TYPE_DES: DESKey des_key = (DESKey) getKey(key_nb, key_type, key_size); if (bytesLeft < 2) ISOException.throwIt(SW_INVALID_PARAMETER); blob_size = Util.getShort(buffer, dataOffset); dataOffset += (short) 2; // Skip Key Size bytesLeft -= (short) 2; if (bytesLeft < blob_size) ISOException.throwIt(SW_INVALID_PARAMETER); des_key.setKey(buffer, dataOffset); dataOffset += blob_size; // Skip Key Value bytesLeft -= blob_size; break; case KeyBuilder.TYPE_AES: AESKey aes_key = (AESKey) getKey(key_nb, key_type, key_size); if (bytesLeft < 2) ISOException.throwIt(SW_INVALID_PARAMETER); blob_size = Util.getShort(buffer, dataOffset); dataOffset += (short) 2; // Skip Key Size bytesLeft -= (short) 2; if (bytesLeft < blob_size) ISOException.throwIt(SW_INVALID_PARAMETER); aes_key.setKey(buffer, dataOffset); dataOffset += blob_size; // Skip Key Value bytesLeft -= blob_size; break; default: ISOException.throwIt(SW_INCORRECT_ALG); }// end switch } /** * This function returns the public key associated with a particular private key stored * in the applet. The exact key blob contents depend on the key�s algorithm and type. * * ins: 0x35 * p1: private key number (0x00-0x0F) * p2: 0x00 * data: none * return(SECP256K1): [coordx_size(2b) | pubkey_coordx | sig_size(2b) | sig] */ private void getPublicKeyFromPrivate(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 (!authorizeKeyOp(key_nb, ACL_READ)) ISOException.throwIt(SW_UNAUTHORIZED); // check type byte key_type = key.getType(); switch(key_type){ case KeyBuilder.TYPE_EC_FP_PRIVATE: if (key.getSize()!= LENGTH_EC_FP_256) ISOException.throwIt(SW_INCORRECT_ALG); // check the curve param if(!Secp256k1.checkCurveParameters((ECPrivateKey)key, recvBuffer, (short)0)) ISOException.throwIt(SW_INCORRECT_ALG); // compute the corresponding partial public key... keyAgreement.init((ECPrivateKey)key); short coordx_size = keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)2); // compute x coordinate of public key as k*G Util.setShort(buffer, (short)0, coordx_size); // sign fixed message sigECDSA.init(key, Signature.MODE_SIGN); short sign_size= sigECDSA.sign(buffer, (short)0, (short)(coordx_size+2), buffer, (short)(coordx_size+4)); Util.setShort(buffer, (short)(coordx_size+2), sign_size); // return x-coordinate of public key+signature // the client can recover full public-key from the signature or // by guessing the compression value () and verifying the signature... apdu.setOutgoingAndSend((short) 0, (short)(2+coordx_size+2+sign_size)); break; default: ISOException.throwIt(SW_INCORRECT_ALG); }// end switch } // 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 (!authorizeKeyOp(key_nb, ACL_READ)) // 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, key_type); // 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, ECC // */ // short bn_size = (short) (keys[key_nb].getSize() / 8 + 2); // switch (key_type) { // case KeyBuilder.TYPE_EC_FP_PUBLIC: // ECPublicKey ec_pub_key = (ECPublicKey) key; // if (avail < bn_size) // ThrowDeleteObjects(SW_NO_MEMORY_LEFT); // size = ec_pub_key.getW(mem.getBuffer(), (short) (base + 2)); // mem.setShort(base, size); // base += (short) (2 + size); // Skip Modulus Size & Value // avail -= (short) (2 + size); // break; // 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; // 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)); // } /** * This function creates a PIN with parameters specified by the P1, P2 and DATA * values. P2 specifies the maximum number of consecutive unsuccessful * verifications before the PIN blocks. PIN can be created only if one of the logged identities * allows it. * * ins: 0x40 * p1: PIN number (0x00-0x07) * p2: max attempt number * data: [PIN_size(1b) | PIN | UBLK_size(1b) | UBLK] * return: none */ 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); } /** * This function verifies a PIN number sent by the DATA portion. The length of * this PIN is specified by the value contained in P3. * Multiple consecutive unsuccessful PIN verifications will block the PIN. If a PIN * blocks, then an UnblockPIN command can be issued. * * ins: 0x42 * p1: PIN number (0x00-0x07) * p2: 0x00 * data: [PIN] * return: none (throws an exception in case of wrong PIN) */ 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); } /** * This function changes a PIN code. The DATA portion contains both the old and * the new PIN codes. * * ins: 0x44 * p1: PIN number (0x00-0x07) * p2: 0x00 * data: [PIN_size(1b) | old_PIN | PIN_size(1b) | new_PIN ] * return: none (throws an exception in case of wrong PIN) */ 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)); } /** * This function unblocks a PIN number using the unblock code specified in the * DATA portion. The P3 byte specifies the unblock code length. * * ins: 0x46 * p1: PIN number (0x00-0x07) * p2: 0x00 * data: [PUK] * return: none (throws an exception in case of wrong PUK) */ 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(); } /** * This function creates an object that will be identified by the provided object ID. * The object�s space and name will be allocated until deleted using MSCDeleteObject. * The object will be allocated upon the card's memory heap. * Object creation is only allowed if the object ID is available and logged in * identity(-ies) have sufficient privileges to create objects. * * ins: 0x5A * p1: 0x00 * p2: 0x00 * data: [object_id(4b) | object_size(4b) | object_ACL(6b)] * where ACL is Read-Write-Delete * return: none */ 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)); } /** * This function deletes the object identified by the provided object ID. The object�s * space and name will be removed from the heap and made available for other objects. * The zero flag denotes whether the object�s memory should be zeroed after * deletion. This kind of deletion is recommended if object was storing sensitive data. * * ins: 0x52 * p1: 0x00 * p2: 0x00 or 0x01 for secure erasure * data: [object_id(4b)] * return: none */ 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); } /** * This function reads data from an object that has been previously created with * MSCCreateObject. Object data is read starting from the byte specified by the * Offset parameter. Up to 255 bytes can be transferred with a single APDU. * If more bytes need to be transferred, then multiple ReadObject commands must * be used with different offsets. * Object data will be effectively read only if logged in identity(ies) have * sufficient privileges for the operation, according to the object�s ACL. * * ins: 0x56 * p1: 0x00 * p2: 0x00 * data: [object_id(4b) | object_offset(4b) | chunk_length(1b)] * return: [object_data(chunk_length)] */ 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); } /** * This function returns the size of an object that has been previously created with * MSCCreateObject. * * ins: 0x57 * p1: 0x00 * p2: 0x00 * data: [object_id(4b)] * return: [object_size(2b)] */ private void GetObjectSize(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) 4) 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)); short base = om.getBaseAddress(obj_class, obj_id); // Verify that object exists if (base == MemoryManager.NULL_OFFSET) ISOException.throwIt(SW_INVALID_PARAMETER); // get object size short obj_size= om.getSizeFromAddress(base); // Fill the buffer Util.setShort(buffer, (short) 0, obj_size); // Send response apdu.setOutgoingAndSend((short) 0, (short) 2); } /** * This function (over-)writes data to an object that has been previously created with * CreateObject. Provided Object Data is stored starting from the byte specified * by the Offset parameter. The size of provided object data must be exactly (Data * Length � 8) bytes. Provided offset value plus the size of provided Object Data * must not exceed object size. * Up to 246 bytes can be transferred with a single APDU. If more bytes need to be * transferred, then multiple WriteObject commands must be used with different offsets. * * ins: 0x54 * p1: 0x00 * p2: 0x00 * data: [object_id(4b) | object_offset(4b) | data_size(1b) | data] * return: none */ 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(); } /** * This function returns a 2 byte bit mask of the available PINs that are currently in * use. Each set bit corresponds to an active PIN. * * ins: 0x48 * p1: 0x00 * p2: 0x00 * data: none * return: [RFU(1b) | PIN_mask(1b)] */ 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); } /** * This function returns a list of current objects and their properties including id, * size, and access control. This function must be initially called with the reset * option. The function only returns one object information at a time and must be * called in repetition until SW_SUCCESS is returned with no further data. * Applications cannot rely on any special ordering of the sequence of returned objects. * * ins: 0x58 * p1: 0x00 (reset and get first entry) or 0x01 (next entry) * p2: 0x00 * data: none * return: [object_id(4b) | object_size(4b) | object_ACL(6b)] */ 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); } /** * This function returns a list of current keys and their properties including id, type, * size, partner, and access control. This function is initially called with the reset * sequence set for sequence type. The function only returns one object id at a time * and must be called in repetition until SW_SUCCESS is returned. * * ins: 0x3A * p1: 0x00 (reset and get first entry) or 0x01 (next entry) * p2: 0x00 * data: none * return: [key_num(1b) | key_type(1b) | key_partner(1b) | key_size(2b) | key_ACL(6b)] */ 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] = key.getType();// 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 Cipher.MODE_DECRYPT: // byte jc_ciph_alg; // switch (ciph_mode) { // case Cipher.ALG_RSA_NOPAD: // if (key_type != KeyBuilder.TYPE_RSA_PUBLIC) // ISOException.throwIt(SW_INVALID_PARAMETER); // jc_ciph_alg = Cipher.ALG_RSA_NOPAD; // break; // case Cipher.ALG_RSA_PKCS1: // if (key_type != KeyBuilder.TYPE_RSA_PUBLIC) // ISOException.throwIt(SW_INVALID_PARAMETER); // jc_ciph_alg = Cipher.ALG_RSA_PKCS1; // break; // case Cipher.ALG_DES_CBC_NOPAD: // if (key_type != KeyBuilder.TYPE_DES) // ISOException.throwIt(SW_INVALID_PARAMETER); // jc_ciph_alg = Cipher.ALG_DES_CBC_NOPAD; // break; // case Cipher.ALG_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 Signature.MODE_VERIFY: // byte jc_sign_alg; // switch (ciph_mode) { // case Signature.ALG_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); // } // } /** * This function retrieves general information about the Applet running on the smart * card, and useful information about the status of current session, such as object * memory information, currently used number of keys and PIN codes, currently * logged in identities, etc� * * ins: 0x3C * p1: 0x00 * p2: 0x00 * data: none * return: [versions(4b) | secure_memory(4b) | memory(4b) | nb_PIN(1b) | nb_keys(1b) | logged_id(2b)] */ 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) PROTOCOL_MAJOR_VERSION; // Major Card Edge Protocol version n. buffer[pos++] = (byte) PROTOCOL_MINOR_VERSION; // Minor Card Edge Protocol version n. buffer[pos++] = (byte) APPLET_MAJOR_VERSION; // Major Applet version n. buffer[pos++] = (byte) APPLET_MINOR_VERSION; // Minor Applet version n. Util.setShort(buffer, pos, (short) bip32_mem.getBuffer().length); // Total secure mem pos += (short) 2; Util.setShort(buffer, pos, (short) mem.getBuffer().length); // Total mem // L.S. pos += (short) 2; Util.setShort(buffer, pos, bip32_mem.freemem()); // secure mem pos += (short) 2; Util.setShort(buffer, pos, mem.freemem()); // Free mem 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); } // private void computeSha512(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 avail = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); // short avail2 = apdu.setIncomingAndReceive(); // // sha512.reset(); // //sha512.doFinal(data, (short) 0, avail2, buffer, (short)0); // sha512.doFinal(buffer, (short) ISO7816.OFFSET_CDATA, avail2, buffer, (short)0); // // apdu.setOutgoingAndSend((short) 0, Sha2.SHA512_DIGEST_LENGTH); // } private void computeHmac(APDU apdu, byte[] buffer) { if (buffer[ISO7816.OFFSET_P1] != (byte)20 && buffer[ISO7816.OFFSET_P1] != (byte)64) 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); short pos= ISO7816.OFFSET_CDATA;//apdu.getOffsetCdata(); //(short) ISO7816.OFFSET_CDATA; short key_size=Util.getShort(buffer, pos); pos+=2; pos+=key_size; short msg_size=Util.getShort(buffer, pos); pos+=2; short hashSize=0; if (buffer[ISO7816.OFFSET_P1]==(byte)20) hashSize= HmacSha160.computeHmacSha160(buffer, (short)(ISO7816.OFFSET_CDATA+2), key_size, buffer, pos, msg_size, buffer, (short)0); else if (buffer[ISO7816.OFFSET_P1]==(byte)64) hashSize= HmacSha512.computeHmacSha512(buffer, (short)(ISO7816.OFFSET_CDATA+2), key_size, buffer, pos, msg_size, buffer, (short)0); apdu.setOutgoingAndSend((short) 0, hashSize); return; } /** * This function imports a Bip32 seed to the applet and derives the master key and chain code. * It also derives a second ECC that uniquely authenticates the HDwallet: the authentikey. * Lastly, it derives a 32-bit AES key that is used to encrypt/decrypt Bip32 object stored in secure memory * If the seed already exists, it is reset if the logged identities allow it. * * The function returns the x-coordinate of the authentikey, self-signed. * The authentikey full public key can be recovered from the signature. * * ins: 0x6C * p1: 0x00 * p2: 0x00 * data: [seed_ACL(6b) | seed_size(1b) | seed_data] * return: [coordx_size(2b) | coordx | sig_size(2b) | sig] */ private void importBIP32Seed(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 bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); if (bytesLeft != apdu.setIncomingAndReceive()) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); /* Check that logged identities are allowed to create seed*/ if ((create_key_ACL == (byte) 0xFF) || (((logged_ids & create_key_ACL) == (short) 0x0000) && (create_key_ACL != (byte) 0x00))) ISOException.throwIt(SW_UNAUTHORIZED); // if seed is already initialized, check that logged identities are allowed to rewwrite seed (and master key) if (bip32_seeded && !authorizeKeyOp(bip32_masterACL, ACL_WRITE)) ISOException.throwIt(SW_UNAUTHORIZED); // buffer data = [6-byte ACL | 1-byte seed size (in byte) | seed data] // get keyACL short offset= (short)ISO7816.OFFSET_CDATA; Util.arrayCopyNonAtomic(buffer, offset, bip32_masterACL, (short)0, (short)KEY_ACL_SIZE); offset+= KEY_ACL_SIZE; // get seed bytesize (max 64 bytes) byte bip32_seedsize = buffer[offset]; offset++; if (bip32_seedsize <0 || bip32_seedsize>64) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); // if seed was already defined, we must clear all related objects!! short nb_deleted=0; if (bip32_seeded){ nb_deleted= clearAllBip32ExtendedKey(recvBuffer, (short)0);} // derive master key! HmacSha512.computeHmacSha512(BITCOIN_SEED, (short)0, (short)BITCOIN_SEED.length, buffer, offset, (short)bip32_seedsize, recvBuffer, (short)0); bip32_masterkey.setKey(recvBuffer, (short)0); // data must be exactly 32 bytes long bip32_masterchaincode.setKey(recvBuffer, (short)32); // data must be exactly 32 bytes long // derive 2 more keys from seed: // - AES encryption key for secure storage of extended keys in object // - ECC key for authentication of sensitive data returned by the applet (hash, pubkeys) HmacSha512.computeHmacSha512(BITCOIN_SEED2, (short)0, (short)BITCOIN_SEED2.length, buffer, offset, (short)bip32_seedsize, recvBuffer, (short)64); bip32_authentikey.setS(recvBuffer, (short)64, BIP32_KEY_SIZE); bip32_encryptkey.setKey(recvBuffer, (short)96); // AES-128: 16-bytes key!! // bip32 is now seeded bip32_seeded= true; // clear recvBuffer Util.arrayFillNonAtomic(recvBuffer, (short)0, (short)128, (byte)0); // compute the partial authentikey public key... keyAgreement.init(bip32_authentikey); short coordx_size = keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)2); // compute x coordinate of public key as k*G Util.setShort(buffer, (short)0, coordx_size); // self signed public key sigECDSA.init(bip32_authentikey, Signature.MODE_SIGN); short sign_size= sigECDSA.sign(buffer, (short)0, (short)(coordx_size+2), buffer, (short)(coordx_size+4)); Util.setShort(buffer, (short)(2+coordx_size), sign_size); Util.setShort(buffer, (short)(2+coordx_size+2+sign_size), nb_deleted); // return x-coordinate of public key+signature // the client can recover full public-key from the signature or // by guessing the compression value () and verifying the signature... // buffer= [coordx_size(2) | coordx | sigsize(2) | sig | nb_deleted(2)] apdu.setOutgoingAndSend((short) 0, (short)(2+coordx_size+2+sign_size+2)); } /** * This function returns the authentikey public key (uniquely derived from the Bip32 seed). * The function returns the x-coordinate of the authentikey, self-signed. * The authentikey full public key can be recovered from the signature. * * ins: 0x73 * p1: 0x00 * p2: 0x00 * data: none * return: [coordx_size(2b) | coordx | sig_size(2b) | sig] */ private void getBIP32AuthentiKey(APDU apdu, byte[] buffer){ // check whether the seed is initialized if (!bip32_seeded) ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED); // compute the partial authentikey public key... keyAgreement.init(bip32_authentikey); short coordx_size = keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)2); // compute x coordinate of public key as k*G Util.setShort(buffer, (short)0, coordx_size); // self signed public key sigECDSA.init(bip32_authentikey, Signature.MODE_SIGN); short sign_size= sigECDSA.sign(buffer, (short)0, (short)(coordx_size+2), buffer, (short)(coordx_size+4)); Util.setShort(buffer, (short)(coordx_size+2), sign_size); // return x-coordinate of public key+signature // the client can recover full public-key from the signature or // by guessing the compression value () and verifying the signature... // buffer= [coordx_size(2) | coordx | sigsize(2) | sig] apdu.setOutgoingAndSend((short) 0, (short)(coordx_size+sign_size+4)); } /** * The function computes the Bip32 extended key derived from the master key and returns the * x-coordinate of the public key signed by the authentikey. * Only hardened keys are currently supported!! * Extended key is stored in the chip in a temporary EC key, along with corresponding ACL * Extended key and chaincode is also cached as a Bip32 object is secure memory * * ins: 0x6D * p1: depth of the extended key (master is depth 0, m/i is depht 1). Max depth is 10 * p2: 0x00 (default) or 0xFF (erase all Bip32 objects from secure memory) * data: index path from master to extended key (m/i/j/k/...). 4 bytes per index * * returns: [coordx_size(2b) | coordx | sig_size(2b) | sig] * * */ private void getBIP32ExtendedKey(APDU apdu, byte[] buffer){ // check master key ACL for computation use (derivation) // to do: define a more flexible approach based on parent object ACL? if (!authorizeKeyOp(bip32_masterACL, ACL_USE)) ISOException.throwIt(SW_UNAUTHORIZED); // input short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); if (bytesLeft != apdu.setIncomingAndReceive()) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); byte bip32_depth = buffer[ISO7816.OFFSET_P1]; if ((bip32_depth < 1) || (bip32_depth > MAX_BIP32_DEPTH) ) ISOException.throwIt(SW_INCORRECT_P1); if (bytesLeft < 4*bip32_depth) ISOException.throwIt(SW_INVALID_PARAMETER); // DONE: encrypt by default! this field is currently not used (RFU)... // TO DO: flag whether to store (save) key as object (currently by default)? byte RFU = buffer[ISO7816.OFFSET_P2]; if (RFU==(byte)0xFF && authorizeKeyOp(bip32_masterACL, ACL_WRITE)) clearAllBip32ExtendedKey(recvBuffer, (short)0); // check whether the seed is seed is initialized if (!bip32_seeded) ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED); // master key data (usefull as parent's data for key derivation) // The method uses a temporary buffer recvBuffer to store the parent and extended key object data: // recvBuffer=[ parent_chain_code (32b) | 0x00 | parent_key (32b) | hash(address)(32b) | current_extended_key(32b) | current_chain_code(32b) | parent_pubkey(65b) | bip32_path(40b)] // hash(address)= [ index(4b) | unused (16b)| crc (4b) | ANTICOLLISIONHASHTMP(4b)| ANTICOLLISIONHASH(4b)] // parent_pubkey(65b)= [compression_byte(1b) | coord_x (32b) | coord_y(32b)] bip32_masterchaincode.getKey(recvBuffer, BIP32_OFFSET_PARENT_CHAINCODE); bip32_masterkey.getKey(recvBuffer,BIP32_OFFSET_PARENT_KEY); recvBuffer[BIP32_OFFSET_PARENT_SEPARATOR]=0x00; // separator, also facilitate HMAC derivation Util.arrayCopyNonAtomic(buffer, ISO7816.OFFSET_CDATA, recvBuffer, BIP32_OFFSET_PATH, (short)(4*bip32_depth)); short parent_base=MemoryManager.NULL_OFFSET; // iterate on indexes provided for (byte i=1; i<=bip32_depth; i++){ //compute SHA of the extended key address up to depth i (only the last bytes are actually used) sha256.reset(); sha256.doFinal(recvBuffer, BIP32_OFFSET_PATH, (short)(i*4), recvBuffer, BIP32_OFFSET_INDEX); short crc1=Util.getShort(recvBuffer, BIP32_OFFSET_CRC); short crc2=Util.getShort(recvBuffer, (short)(BIP32_OFFSET_CRC+2)); short base=bip32_om.getBaseAddress(crc1, crc2); while(base!=MemoryManager.NULL_OFFSET){ // there is already an object at this address //extract anti-collision hash of object bip32_mem.getBytes(recvBuffer, BIP32_OFFSET_COLLISIONHASHTMP, base, (short)0, BIP32_ANTICOLLISION_LENGTH); // check whether it is the correct object or just a collision if (Util.arrayCompare(recvBuffer, BIP32_OFFSET_COLLISIONHASHTMP, recvBuffer, BIP32_OFFSET_COLLISIONHASH, BIP32_ANTICOLLISION_LENGTH)==0){ // copy object data to temporary buffer bip32_mem.getBytes(recvBuffer, BIP32_OFFSET_COLLISIONHASH, base, (short)0, BIP32_OBJECT_SIZE); // exit loop with base pointing to the correct object break; }else{ // collision // try next crc address until correct object or no object is found... Biginteger.add1_carry(recvBuffer, BIP32_OFFSET_CRC, (short) 4); crc1=Util.getShort(recvBuffer, BIP32_OFFSET_CRC); crc2=Util.getShort(recvBuffer, (short)(BIP32_OFFSET_CRC+2)); base=bip32_om.getBaseAddress(crc1, crc2); } } //end of while // at this point, either base points to the correct object, of the object does not exist // create object if no object was found if (base==MemoryManager.NULL_OFFSET){ // normal or hardened child? byte msb= recvBuffer[(short)(BIP32_OFFSET_PATH+4*(i-1))]; if ((msb & 0x80)!=0x80){ // normal child // we must compute parent's compressed pubkey from privkey // check if parent's compression byte is available byte compbyte=0x04; if (parent_base==MemoryManager.NULL_OFFSET) compbyte=bip32_master_compbyte; else compbyte=bip32_mem.getByte(parent_base, (short)(BIP32_OBJECT_SIZE-1)); // compute coord x from privkey bip32_extendedkey.setS(recvBuffer, BIP32_OFFSET_PARENT_KEY, BIP32_KEY_SIZE); keyAgreement.init(bip32_extendedkey); keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, BIP32_OFFSET_PUBX); // compute compbyte from coord y if necessary if (compbyte==0x04){ // coord y= square root of X^3+7 mod p => 2 solutions! EccComputation.SqrtRootOpt(recvBuffer, BIP32_OFFSET_PUBX, recvBuffer, BIP32_OFFSET_PUBY); recvBuffer[BIP32_OFFSET_PUB]=0x04; // sign a dummy message sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN); short sigsize=sigECDSA.sign(recvBuffer, (short)0, (short)32, buffer, BIP32_OFFSET_SIG); // verify sig with pubkey (x,y) & recover compression byte bip32_pubkey.setW(recvBuffer, BIP32_OFFSET_PUB, (short)(2*BIP32_KEY_SIZE+1)) ; sigECDSA.init(bip32_pubkey, Signature.MODE_VERIFY); boolean verify= sigECDSA.verify(recvBuffer, (short)0, (short)32, buffer, BIP32_OFFSET_SIG, sigsize); boolean parity= ((recvBuffer[(short)(BIP32_OFFSET_PUBY+31)]&0x01)==0); compbyte= (verify^parity)?(byte)0x03:(byte)0x02; // save compbyte in parent's object for future use if (parent_base==MemoryManager.NULL_OFFSET) bip32_master_compbyte= compbyte; else bip32_mem.setByte(parent_base, (short)(BIP32_OBJECT_SIZE-1), compbyte); } // compute HMAC of compressed pubkey + index recvBuffer[BIP32_OFFSET_PUB]= compbyte; Util.arrayCopyNonAtomic(recvBuffer, (short)(BIP32_OFFSET_PATH+4*(i-1)), recvBuffer, BIP32_OFFSET_PUBY, (short)4); HmacSha512.computeHmacSha512(recvBuffer, BIP32_OFFSET_PARENT_CHAINCODE, BIP32_KEY_SIZE, recvBuffer, BIP32_OFFSET_PUB, (short)(1+BIP32_KEY_SIZE+4), recvBuffer, BIP32_OFFSET_CHILD_KEY); } else { // hardened child recvBuffer[BIP32_KEY_SIZE]= 0x00; Util.arrayCopyNonAtomic(recvBuffer, (short)(BIP32_OFFSET_PATH+4*(i-1)), recvBuffer, BIP32_OFFSET_INDEX, (short)4); HmacSha512.computeHmacSha512(recvBuffer, BIP32_OFFSET_PARENT_CHAINCODE, BIP32_KEY_SIZE, recvBuffer, BIP32_OFFSET_PARENT_SEPARATOR, (short)(1+BIP32_KEY_SIZE+4), recvBuffer, BIP32_OFFSET_CHILD_KEY); } // addition with parent_key... // First check that parse256(IL) < SECP256K1_R if(!Biginteger.lessThan(recvBuffer, BIP32_OFFSET_CHILD_KEY, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, BIP32_KEY_SIZE)){ ISOException.throwIt(SW_BIP32_DERIVATION_ERROR); } // add parent_key (mod SECP256K1_R) if(Biginteger.add_carry(recvBuffer, BIP32_OFFSET_CHILD_KEY, recvBuffer, (short) (BIP32_KEY_SIZE+1), BIP32_KEY_SIZE)){ // in case of final carry, we must substract SECP256K1_R // we have IL<SECP256K1_R and parent_key<SECP256K1_R, so IL+parent_key<2*SECP256K1_R Biginteger.subtract(recvBuffer, BIP32_OFFSET_CHILD_KEY, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, BIP32_KEY_SIZE); }else{ // in the unlikely case where SECP256K1_R<=IL+parent_key<2^256 if(!Biginteger.lessThan(recvBuffer, BIP32_OFFSET_CHILD_KEY, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, BIP32_KEY_SIZE)){ Biginteger.subtract(recvBuffer, BIP32_OFFSET_CHILD_KEY, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, BIP32_KEY_SIZE); } // check that value is not 0 if(Biginteger.equalZero(recvBuffer, BIP32_OFFSET_CHILD_KEY, BIP32_KEY_SIZE)){ ISOException.throwIt(SW_BIP32_DERIVATION_ERROR); } } // encrypt privkey & chaincode aes128.init(bip32_encryptkey, Cipher.MODE_ENCRYPT); aes128.doFinal(recvBuffer, BIP32_OFFSET_CHILD_KEY, (short)(2*BIP32_KEY_SIZE), recvBuffer, BIP32_OFFSET_CHILD_KEY); // create object if logged identities have the correct ACL // to do: check the object ACL // to do: create object for transaction keys in last index (since they are usually used only once)? base= bip32_om.createObject(crc1, crc2, BIP32_OBJECT_SIZE, bip32_masterACL, (short) 0); // Enforce Access Control if (!bip32_om.authorizeWriteFromAddress(base, logged_ids)) ISOException.throwIt(SW_UNAUTHORIZED); // Update object data recvBuffer[BIP32_OFFSET_PUB]=0x04; bip32_mem.setBytes(base, (short)0, recvBuffer, BIP32_OFFSET_COLLISIONHASH, BIP32_OBJECT_SIZE); }//end if (object creation) // at this point, recvBuffer contains a copy of the object related to extended key at depth i // decrypt privkey & chaincode as they are encrypted at this point aes128.init(bip32_encryptkey, Cipher.MODE_DECRYPT); aes128.doFinal(recvBuffer, BIP32_OFFSET_CHILD_KEY, (short)(2*BIP32_KEY_SIZE), recvBuffer, BIP32_OFFSET_CHILD_KEY); // copy privkey & chain code in parent's offset Util.arrayCopyNonAtomic(recvBuffer, BIP32_OFFSET_CHILD_CHAINCODE, recvBuffer, BIP32_OFFSET_PARENT_CHAINCODE, BIP32_KEY_SIZE); // chaincode Util.arrayCopyNonAtomic(recvBuffer, BIP32_OFFSET_CHILD_KEY, recvBuffer, BIP32_OFFSET_PARENT_KEY, BIP32_KEY_SIZE); // extended_key recvBuffer[BIP32_KEY_SIZE]=0x00; // update parent_base for next iteration parent_base=base; } // end for // at this point, recvBuffer contains a copy of the last extended key // instantiate elliptic curve with last extended key + copy ACL bip32_extendedkey.setS(recvBuffer, BIP32_OFFSET_PARENT_KEY, BIP32_KEY_SIZE); Util.arrayCopyNonAtomic(bip32_masterACL, (short)0, bip32_extendedACL, (short)0, KEY_ACL_SIZE); // clear recvBuffer Util.arrayFillNonAtomic(recvBuffer, BIP32_OFFSET_PARENT_CHAINCODE, BIP32_OFFSET_END, (byte)0); // compute the corresponding partial public key... keyAgreement.init(bip32_extendedkey); short coordx_size = keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)2); // compute x coordinate of public key as k*G Util.setShort(buffer, (short)0, coordx_size); // self-sign coordx sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN); short sign_size= sigECDSA.sign(buffer, (short)0, (short)(coordx_size+2), buffer, (short)(coordx_size+4)); Util.setShort(buffer, (short)(coordx_size+2), sign_size); // coordx signed by authentikey sigECDSA.init(bip32_authentikey, Signature.MODE_SIGN); short sign_size2= sigECDSA.sign(buffer, (short)0, (short)(coordx_size+sign_size+4), buffer, (short)(coordx_size+sign_size+6)); Util.setShort(buffer, (short)(coordx_size+sign_size+4), sign_size2); // return x-coordinate of public key+signatures // the client can recover full public-key by guessing the compression value () and verifying the signature... // buffer=[coordx_size(2) | coordx | sign_size(2) | self-sign | sign_size(2) | auth_sign] apdu.setOutgoingAndSend((short) 0, (short)(coordx_size+sign_size+sign_size2+6)); }// end of getBip32ExtendedKey() /** * The function clears all the Bip32 objects from secure memory. * This is done during either Bip32 seed import (when a new seed is imported) or when a new * extended key is computed (when p2 flag 0xFF is invoked). */ private short clearAllBip32ExtendedKey(byte[] tmpBuffer, short offset){ short nb_deleted=0; short obj_cla, obj_id; while (bip32_om.getFirstRecord(tmpBuffer, offset)){ // get obj_class and obj_id obj_cla= Util.getShort(tmpBuffer, offset); obj_id= Util.getShort(tmpBuffer, (short)(offset+2)); bip32_om.destroyObject(obj_cla, obj_id, true); nb_deleted++; } return nb_deleted; } /** * This function signs Bitcoin message using std or Bip32 extended key * * ins: 0x6E * p1: key number or 0xFF for the last derived Bip32 extended key * p2: Init-Update-Finalize * data(init): none * data(update/finalize): [chunk_size(2b) | chunk_data] * * returns(init/update): none * return(finalize): [sig] * */ private void signMessage(APDU apdu, byte[] buffer){ byte key_nb = buffer[ISO7816.OFFSET_P1]; if ( (key_nb!=(byte)0xFF) && ((key_nb < 0)||(key_nb >= MAX_NUM_KEYS)) ) ISOException.throwIt(SW_INCORRECT_P1); byte p2= buffer[ISO7816.OFFSET_P2]; if (p2 <= (byte) 0x00 || p2 > (byte) 0x03) 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); // chek ACL if (key_nb==(byte)0xFF && !authorizeKeyOp(bip32_extendedACL, ACL_USE)) ISOException.throwIt(SW_UNAUTHORIZED); if (key_nb!=(byte)0xFF && !authorizeKeyOp(key_nb, ACL_USE)) ISOException.throwIt(SW_UNAUTHORIZED); // check whether the seed is initialized if (key_nb==(byte)0xFF && !bip32_seeded) ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED); short chunk_size, offset, recvOffset; switch(p2){ // initialization case OP_INIT: // copy message header to tmp buffer Util.arrayCopyNonAtomic(BITCOIN_SIGNED_MESSAGE_HEADER, (short)0, recvBuffer, (short)0, (short)BITCOIN_SIGNED_MESSAGE_HEADER.length); recvOffset= (short)BITCOIN_SIGNED_MESSAGE_HEADER.length; // buffer data = [4-byte msg_size] offset= (short)ISO7816.OFFSET_CDATA; recvOffset+= Biginteger.encodeVarInt(buffer, offset, recvBuffer, recvOffset); offset+=4; sha256.reset(); sha256.update(recvBuffer, (short) 0, recvOffset); sign_flag= true; // set flag break; // update (optionnal) case OP_PROCESS: if (!sign_flag) ISOException.throwIt(SW_INCORRECT_INITIALIZATION); // buffer data = [2-byte chunk_size | n-byte message to sign] offset= (short)ISO7816.OFFSET_CDATA; chunk_size=Util.getShort(buffer, offset); offset+=2; sha256.update(buffer, (short) offset, chunk_size); break; // final case OP_FINALIZE: if (!sign_flag) ISOException.throwIt(SW_INCORRECT_INITIALIZATION); // buffer data = [2-byte chunk_size | n-byte message to sign] offset= (short)ISO7816.OFFSET_CDATA; chunk_size=Util.getShort(buffer, offset); offset+=2; sha256.doFinal(buffer, (short) offset, chunk_size, buffer, (short) 0); sign_flag= false;// reset flag // set key & sign if (key_nb==(byte)0xFF) sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN); else{ Key key= keys[key_nb]; if (key.getType()!=KeyBuilder.TYPE_EC_FP_PRIVATE) ISOException.throwIt(SW_INCORRECT_ALG); sigECDSA.init(key, Signature.MODE_SIGN); } short sign_size= sigECDSA.sign(buffer, (short)0, (short)32, buffer, (short)0); apdu.setOutgoingAndSend((short) 0, sign_size); break; } } /** * This function signs short Bitcoin message using std or Bip32 extended key in 1 APDU * * ins: 0x72 * p1: key number or 0xFF for the last derived Bip32 extended key * p2: 0x00 * data: [msg_size(2b) | msg_data] * * return: [sig] * */ private void signShortMessage(APDU apdu, byte[] buffer){ byte key_nb = buffer[ISO7816.OFFSET_P1]; if ( (key_nb!=(byte)0xFF) && ((key_nb < 0)||(key_nb >= MAX_NUM_KEYS)) ) // debug!! 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); // chek ACL if (key_nb==(byte)0xFF && !authorizeKeyOp(bip32_extendedACL, ACL_USE)) ISOException.throwIt(SW_UNAUTHORIZED); if (key_nb!=(byte)0xFF && !authorizeKeyOp(key_nb, ACL_USE)) ISOException.throwIt(SW_UNAUTHORIZED); // check whether the seed is initialized if (key_nb==(byte)0xFF && !bip32_seeded) ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED); // copy message header to tmp buffer Util.arrayCopyNonAtomic(BITCOIN_SIGNED_MESSAGE_HEADER, (short)0, recvBuffer, (short)0, (short)BITCOIN_SIGNED_MESSAGE_HEADER.length); short recvOffset= (short)BITCOIN_SIGNED_MESSAGE_HEADER.length; // buffer data = [2-byte size | n-byte message to sign] short offset= (short)ISO7816.OFFSET_CDATA; short msgSize= Util.getShort(buffer, offset); recvOffset+= Biginteger.encodeShortToVarInt(msgSize, recvBuffer, recvOffset); offset+=2; Util.arrayCopyNonAtomic(buffer, offset, recvBuffer, recvOffset, msgSize); offset+= msgSize; recvOffset+= msgSize; // hash SHA-256 sha256.reset(); sha256.doFinal(recvBuffer, (short) 0, recvOffset, recvBuffer, (short) 0); // set key & sign if (key_nb==(byte)0xFF) sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN); else{ Key key= keys[key_nb]; if (key.getType()!=KeyBuilder.TYPE_EC_FP_PRIVATE) ISOException.throwIt(SW_INCORRECT_ALG); sigECDSA.init(key, Signature.MODE_SIGN); } short sign_size= sigECDSA.sign(recvBuffer, (short)0, (short)32, buffer, (short)0); apdu.setOutgoingAndSend((short) 0, sign_size); } // // mainly for testing... // private void setBIP32ExtendedKey(APDU apdu, byte[] buffer){ // // // set default private point // byte[] key_data={ // (byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00, // (byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x01 // }; // bip32_extendedkey.setS(key_data, (short)0, BIP32_KEY_SIZE); // // // compute the corresponding partial public key... // keyAgreement.init(bip32_extendedkey); // short coordx_size = keyAgreement.generateSecret(SECP256K1_G, (short) 0, (short) SECP256K1_G.length, buffer, (short)2); // compute x coordinate of public key as k*G // Util.setShort(buffer, (short)0, coordx_size); // // // sign fixed message // sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN); // short sign_size= sigECDSA.sign(buffer, (short)0, (short)(coordx_size+2), buffer, (short)(coordx_size+4)); // Util.setShort(buffer, (short)(coordx_size+2), sign_size); // // // return x-coordinate of public key+signature // // the client can recover full public-key from the signature or // // by guessing the compression value () and verifying the signature... // apdu.setOutgoingAndSend((short) 0, (short)(coordx_size+sign_size+4)); // // } /** * This function parses a raw transaction and returns the corresponding double SHA-256 * If the Bip32 seed is initialized, the hash is signed with the authentikey. * * ins: 0x71 * p1: Init or Process * p2: 0x00 * data: [raw_tx] * * return: [hash(32b) | needs_confirm(1b) | sig_size(2b) | sig ] * * where: * needs_confirm is 0x01 if a hmac-sha1 of the hash must be provided for tx signing */ private void ParseTransaction(APDU apdu, byte[] buffer){ byte p1 = buffer[ISO7816.OFFSET_P1]; byte p2 = buffer[ISO7816.OFFSET_P2]; short dataOffset = ISO7816.OFFSET_CDATA; short dataRemaining = (short)(buffer[ISO7816.OFFSET_LC] & 0xff); if (p1== OP_INIT){ // initialize transaction object Transaction.resetTransaction(); } // parse the transaction byte result = Transaction.parseTransaction(buffer, dataOffset, dataRemaining); if (result == Transaction.RESULT_ERROR) { Transaction.resetTransaction(); ISOException.throwIt(ISO7816.SW_WRONG_DATA); } else if (result == Transaction.RESULT_MORE) { short offset = 0; // Transaction context Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_CURRENT_I, buffer, offset, Transaction.SIZEOF_U32); offset += 4; Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_CURRENT_O, buffer, offset, Transaction.SIZEOF_U32); offset += 4; Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_SCRIPT_COORD, buffer, offset, Transaction.SIZEOF_U32); offset += 4; Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_A_TRANSACTION_AMOUNT, buffer, offset, Transaction.SIZEOF_AMOUNT); offset += Transaction.SIZEOF_AMOUNT; // not so relevant context info mainly for debugging (not sensitive) // if (DEBUG_MODE){ // Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_REMAINING_I, buffer, offset, Transaction.SIZEOF_U32); // offset += 4; // Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_REMAINING_O, buffer, offset, Transaction.SIZEOF_U32); // offset += 4; // Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_SCRIPT_REMAINING, buffer, offset, Transaction.SIZEOF_U32); // offset += 4; // Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_TMP_BUFFER, buffer, offset, Transaction.SIZEOF_U32); // offset += Transaction.SIZEOF_AMOUNT; // Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_SCRIPT_ACTIVE, buffer, offset, Transaction.SIZEOF_U8); // offset += 1; // Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_B_TRANSACTION_STATE, buffer, offset, Transaction.SIZEOF_U8); // offset += 1; // Util.setShort(buffer, offset, dataOffset); // offset+=2; // Util.setShort(buffer, offset, dataRemaining); // offset+=2; // } apdu.setOutgoingAndSend((short)0, offset); return; } else if (result == Transaction.RESULT_FINISHED) { // check whether 2fa is required (hmac-sha1 of tx hash) short need2fa=(short)0x0000; Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_A_TRANSACTION_AMOUNT, transactionData, OFFSET_TRANSACTION_AMOUNT, (short)8); Biginteger.add_carry(transactionData, OFFSET_TRANSACTION_AMOUNT, transactionData, OFFSET_TRANSACTION_TOTAL, (short)8); if ((option_flags & HMAC_CHALRESP_2FA)==HMAC_CHALRESP_2FA){ if (Biginteger.lessThan(transactionData, OFFSET_TRANSACTION_LIMIT, transactionData, OFFSET_TRANSACTION_AMOUNT, (short)8)){ need2fa^= HMAC_CHALRESP_2FA; // set msb } } // store transaction hash (single hash!) in memory Transaction.digestFull.doFinal(transactionData, (short)0, (short)0, transactionData, OFFSET_TRANSACTION_HASH); // return transaction hash (double hash!) // the msb bit of hash_size is set to 1 if a Hmac confirmation is required for the tx signature sha256.reset(); short hash_size=sha256.doFinal(transactionData, OFFSET_TRANSACTION_HASH, (short)32, buffer, (short)2); Util.setShort(buffer, (short)0, (short)(hash_size+2)); Util.setShort(buffer, (short)(2+hash_size), need2fa); short offset = (short)(2+hash_size+2); // hash signed by authentikey if seed is initialized if (bip32_seeded){ sigECDSA.init(bip32_authentikey, Signature.MODE_SIGN); short sign_size= sigECDSA.sign(buffer, (short)0, offset, buffer, (short)(offset+2)); Util.setShort(buffer, offset, sign_size); offset+=(short)(2+sign_size); }else{ Util.setShort(buffer, offset, (short)0); offset+=(short)2; } // Transaction context //todo: put this context in other method Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_CURRENT_I, buffer, offset, Transaction.SIZEOF_U32); offset += 4; Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_CURRENT_O, buffer, offset, Transaction.SIZEOF_U32); offset += 4; Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_SCRIPT_COORD, buffer, offset, Transaction.SIZEOF_U32); offset += 4; Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_A_TRANSACTION_AMOUNT, buffer, offset, Transaction.SIZEOF_AMOUNT); offset += Transaction.SIZEOF_AMOUNT; // not so relevant context info mainly for debugging (not sensitive) // if (DEBUG_MODE){ // Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_REMAINING_I, buffer, offset, Transaction.SIZEOF_U32); // offset += 4; // Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_REMAINING_O, buffer, offset, Transaction.SIZEOF_U32); // offset += 4; // Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_SCRIPT_REMAINING, buffer, offset, Transaction.SIZEOF_U32); // offset += 4; // Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_TMP_BUFFER, buffer, offset, Transaction.SIZEOF_U32); // offset += Transaction.SIZEOF_AMOUNT; // Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_SCRIPT_ACTIVE, buffer, offset, Transaction.SIZEOF_U8); // offset += 1; // Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_B_TRANSACTION_STATE, buffer, offset, Transaction.SIZEOF_U8); // offset += 1; // Util.setShort(buffer, offset, dataOffset); // offset+=2; // Util.setShort(buffer, offset, dataRemaining); // offset+=2; // } // reset data and send result // buffer= [tx_hash(32) | sign_size(2) | signature | tx context(20 - 46)] Transaction.resetTransaction(); apdu.setOutgoingAndSend((short)0, offset); } return; } /** * This function signs the current hash transaction with a std or the last extended key * The hash provided in the APDU is compared to the version stored inside the chip. * Depending of the total amount in the transaction and the predefined limit, * a HMAC must be provided as an additional security layer. * * ins: 0x6F * p1: key number or 0xFF for the last derived Bip32 extended key * p2: 0x00 * data: [hash(32b) | option:hmac(20b)] * * return: [sig ] * */ private void SignTransaction(APDU apdu, byte[] buffer){ byte key_nb = buffer[ISO7816.OFFSET_P1]; if ( (key_nb!=(byte)0xFF) && ((key_nb < 0) || (key_nb >= MAX_NUM_KEYS)) ) 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<MessageDigest.LENGTH_SHA_256) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if (key_nb==(byte)0xFF && !authorizeKeyOp(bip32_extendedACL, ACL_USE)) ISOException.throwIt(SW_UNAUTHORIZED); if (key_nb!=(byte)0xFF && !authorizeKeyOp(key_nb, ACL_USE)) ISOException.throwIt(SW_UNAUTHORIZED); // check whether the seed is initialized if (key_nb==(byte)0xFF && !bip32_seeded) ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED); // check doublehash value in buffer with cached singlehash value sha256.reset(); sha256.doFinal(transactionData, OFFSET_TRANSACTION_HASH, MessageDigest.LENGTH_SHA_256, recvBuffer, (short)0); if ((byte)0 != Util.arrayCompare(buffer, ISO7816.OFFSET_CDATA, recvBuffer, (short)0, MessageDigest.LENGTH_SHA_256)) ISOException.throwIt(SW_INCORRECT_TXHASH); // check challenge-response answer if necessary if( (option_flags & HMAC_CHALRESP_2FA)==HMAC_CHALRESP_2FA){ if( Biginteger.lessThan(transactionData, OFFSET_TRANSACTION_LIMIT, transactionData, OFFSET_TRANSACTION_AMOUNT, (short)8)){ if (bytesLeft<MessageDigest.LENGTH_SHA_256+MessageDigest.LENGTH_SHA+(short)2) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); // check flag for 2fa_hmac_chalresp short hmac_flags= Util.getShort(buffer, (short)(ISO7816.OFFSET_CDATA+32+2)); if (hmac_flags!=HMAC_CHALRESP_2FA) ISOException.throwIt(SW_INCORRECT_ALG); // hmac of 64-bytes msg: (doublesha256(raw_tx) | 32bytes padding) Util.arrayFillNonAtomic(recvBuffer, (short)32, (short)32, (byte)0x00); HmacSha160.computeHmacSha160(transactionData, OFFSET_TRANSACTION_HMACKEY, (short)20, recvBuffer, (short)0, (short)64, recvBuffer, (short)64); if (Util.arrayCompare(buffer, (short)(ISO7816.OFFSET_CDATA+32+2), recvBuffer, (short)64, (short)20)!=0) ISOException.throwIt(SW_SIGNATURE_INVALID); // reset total amount Util.arrayFillNonAtomic(transactionData, OFFSET_TRANSACTION_TOTAL, (short)8, (byte)0x00); } else{ //update total amount Util.arrayCopyNonAtomic(transactionData, OFFSET_TRANSACTION_AMOUNT, transactionData, OFFSET_TRANSACTION_TOTAL, (short)8); } } // hash+sign singlehash if (key_nb==(byte)0xFF) sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN); else{ Key key= keys[key_nb]; if (key.getType()!=KeyBuilder.TYPE_EC_FP_PRIVATE) ISOException.throwIt(SW_INCORRECT_ALG); sigECDSA.init(key, Signature.MODE_SIGN); } short sign_size= sigECDSA.sign(transactionData, OFFSET_TRANSACTION_HASH, (short)32, buffer, (short)0); apdu.setOutgoingAndSend((short) 0, sign_size); } } // end of class JAVA_APPLET