/*
*******************************************************************************
* 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;
}