/* ******************************************************************************* * Java Card Bitcoin Hardware Wallet * (c) 2015 Ledger * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************* */ package com.ledger.wallet; import javacard.framework.JCSystem; import javacard.framework.Util; import javacard.security.DESKey; import javacard.security.KeyBuilder; /** * Hardware Wallet Security Card implementation * @author BTChip * */ public class Keycard { public static void init() { issuerKeycard = (DESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_2KEY, false); userKeycard = (DESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_2KEY, false); pairingData = JCSystem.makeTransientByteArray((byte)(PAIRING_DATA_SIZE + 1), JCSystem.CLEAR_ON_DESELECT); challenge = JCSystem.makeTransientByteArray((byte)4, JCSystem.CLEAR_ON_DESELECT); } public static void setPairingData(byte[] data, short offset) { pairingData[0] = (byte)0x01; Util.arrayCopyNonAtomic(data, offset, pairingData, (short)1, PAIRING_DATA_SIZE); } public static boolean getPairingData(byte[] data, short offset) { if (pairingData[0] == (byte)0) { return false; } Util.arrayCopyNonAtomic(pairingData, (short)1, data, offset, PAIRING_DATA_SIZE); pairingData[0] = (byte)0x00; return true; } public static void clearPairingData() { pairingData[0] = (byte)0x00; } public static void setIssuer(byte issuerKeycardSize, byte[] buffer, short offset) { issuerKeycard.setKey(buffer, offset); Keycard.issuerKeycardSize = issuerKeycardSize; } public static void setUser(byte userKeycardSize, byte[] buffer, short offset) { userKeycard.setKey(buffer, offset); Keycard.userKeycardSize = userKeycardSize; } public static boolean isInitialized() { return (issuerKeycardSize != (byte)0); } public static boolean isInitializedUser() { return (userKeycardSize != (byte)0); } public static void generateIndexes(byte[] target, short offset, byte addressSize) { byte size = (userKeycardSize != (byte)0 ? userKeycardSize : issuerKeycardSize); for (byte i=0; i<size; i++) { boolean unique = true; do { target[(short)(offset + i)] = Crypto.getRandomByteModulo(addressSize); for (byte k=0; k<i; k++) { if (target[(short)(offset + k)] == target[(short)(offset + i)]) { unique = false; break; } } } while (!unique); } } public static void generateRandomIndexes(byte[] target, short offset, byte randomSize) { for (byte i=0; i<randomSize; i++) { byte index; do { index = Crypto.getRandomByteModulo((short)128); } while(Base58.BASE58TABLE[index] == (byte)0xff); target[(short)(offset + i)] = (byte)(index - 0x30); } } public static boolean check(byte[] address, short addressOffset, byte addressSize, byte[] code, short codeOffset, byte codeSize, byte[] indexes, short indexesOffset, byte[] scratch, short scratchOffset) { byte size = (userKeycardSize != (byte)0 ? userKeycardSize : issuerKeycardSize); DESKey key = (userKeycardSize != (byte)0 ? userKeycard : issuerKeycard); byte i; for (i=0; i<KEYCARD_SIZE; i++) { scratch[(short)(scratchOffset + i)] = i; } Crypto.initCipher(key, true); Crypto.blobEncryptDecrypt.doFinal(scratch, scratchOffset, KEYCARD_SIZE, scratch, scratchOffset); for (i=0; i<KEYCARD_SIZE; i++) { scratch[(short)(scratchOffset + i)] = (byte)(((scratch[(short)(scratchOffset + i)] >> 4) & 0x0f) ^ (scratch[(short)(scratchOffset + i)] & 0x0f)); } for (i=0; i<size; i++) { short addressCode; if (address != null) { addressCode = (short)((address[(short)(addressOffset + indexes[(short)(indexesOffset + i)])] & 0xff) - (short)0x30); } else { addressCode = indexes[(short)(indexesOffset + i)]; } if (code[(short)(codeOffset + i)] != scratch[(short)(scratchOffset + addressCode)]) { return false; } } return true; } private static final byte KEYCARD_SIZE = (byte)0x50; private static final byte PAIRING_DATA_SIZE = (byte)17; protected static byte issuerKeycardSize; private static DESKey issuerKeycard; protected static byte userKeycardSize; private static DESKey userKeycard; protected static byte[] pairingData; protected static byte[] challenge; }