implement duplication

This commit is contained in:
Michele Balistreri 2018-11-21 16:40:48 +03:00
parent 2a72585cbb
commit c4c93b9c02
3 changed files with 56 additions and 27 deletions

View File

@ -3,12 +3,15 @@ package im.status.wallet;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.*;
import javacardx.crypto.Cipher;
/**
* Crypto utilities, mostly BIP32 related. The init method must be called during application installation. This class
* is not meant to be instantiated.
*/
public class Crypto {
final static public short AES_BLOCK_SIZE = 16;
final static private short KEY_SECRET_SIZE = 32;
final static private short KEY_DERIVATION_INPUT_SIZE = 37;
final static private short HMAC_OUT_SIZE = 64;
@ -23,15 +26,18 @@ public class Crypto {
final static private byte[] KEY_BITCOIN_SEED = {'B', 'i', 't', 'c', 'o', 'i', 'n', ' ', 's', 'e', 'e', 'd'};
// The below 4 objects can be accessed anywhere from the entire applet
// The below 5 objects can be accessed anywhere from the entire applet
RandomData random;
KeyAgreement ecdh;
MessageDigest sha256;
MessageDigest sha512;
Cipher aesCbcIso9797m2;
private Signature hmacSHA512;
private HMACKey hmacKey;
private AESKey tmpAES256;
private byte[] tmp;
Crypto() {
@ -39,6 +45,9 @@ public class Crypto {
sha256 = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false);
ecdh = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN, false);
sha512 = MessageDigest.getInstance(MessageDigest.ALG_SHA_512, false);
aesCbcIso9797m2 = Cipher.getInstance(Cipher.ALG_AES_CBC_ISO9797_M2,false);
tmpAES256 = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_AES_256, false);
short blockSize;
@ -54,6 +63,12 @@ public class Crypto {
tmp = JCSystem.makeTransientByteArray((short) (HMAC_BLOCK_OFFSET + blockSize), JCSystem.CLEAR_ON_RESET);
}
public short oneShotAES(byte mode, byte[] src, short sOff, short sLen, byte[] dst, short dOff, byte[] key, short keyOff) {
tmpAES256.setKey(key, keyOff);
aesCbcIso9797m2.init(tmpAES256, mode, src, sOff, AES_BLOCK_SIZE);
return aesCbcIso9797m2.doFinal(src, (short) (sOff + AES_BLOCK_SIZE), sLen, dst, dOff);
}
/**
* Derives a private key according to the algorithm defined in BIP32. The BIP32 specifications define some checks
* to be performed on the derived keys. In the very unlikely event that these checks fail this key is not considered

View File

@ -11,7 +11,7 @@ public class SecureChannel {
public static final short SC_KEY_LENGTH = 256;
public static final short SC_SECRET_LENGTH = 32;
public static final short PAIRING_KEY_LENGTH = SC_SECRET_LENGTH + 1;
public static final short SC_BLOCK_SIZE = 16;
public static final short SC_BLOCK_SIZE = Crypto.AES_BLOCK_SIZE;
public static final short SC_OUT_OFFSET = ISO7816.OFFSET_CDATA + (SC_BLOCK_SIZE * 2);
public static final short SC_COUNTER_MAX = 100;
@ -28,7 +28,6 @@ public class SecureChannel {
private AESKey scEncKey;
private AESKey scMacKey;
private Cipher scCipher;
private Signature scMac;
private KeyPair scKeypair;
private byte[] secret;
@ -55,8 +54,6 @@ public class SecureChannel {
public SecureChannel(byte pairingLimit, Crypto crypto, SECP256k1 secp256k1) {
this.crypto = crypto;
scCipher = Cipher.getInstance(Cipher.ALG_AES_CBC_ISO9797_M2,false);
scMac = Signature.getInstance(Signature.ALG_AES_MAC_128_NOPAD, false);
scEncKey = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_AES_256, false);
@ -104,10 +101,10 @@ public class SecureChannel {
}
scEncKey.setKey(secret, (short) 0);
scCipher.init(scEncKey, Cipher.MODE_DECRYPT, apduBuffer, off, SC_BLOCK_SIZE);
crypto.aesCbcIso9797m2.init(scEncKey, Cipher.MODE_DECRYPT, apduBuffer, off, SC_BLOCK_SIZE);
off = (short)(off + SC_BLOCK_SIZE);
apduBuffer[ISO7816.OFFSET_LC] = (byte) scCipher.doFinal(apduBuffer, off, (short)((short)(apduBuffer[ISO7816.OFFSET_LC] & 0xff) - off + ISO7816.OFFSET_CDATA), apduBuffer, ISO7816.OFFSET_CDATA);
apduBuffer[ISO7816.OFFSET_LC] = (byte) crypto.aesCbcIso9797m2.doFinal(apduBuffer, off, (short)((short)(apduBuffer[ISO7816.OFFSET_LC] & 0xff) - off + ISO7816.OFFSET_CDATA), apduBuffer, ISO7816.OFFSET_CDATA);
}
/**
@ -302,9 +299,9 @@ public class SecureChannel {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
scCipher.init(scEncKey, Cipher.MODE_DECRYPT, secret, (short) 0, SC_BLOCK_SIZE);
crypto.aesCbcIso9797m2.init(scEncKey, Cipher.MODE_DECRYPT, secret, (short) 0, SC_BLOCK_SIZE);
Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, secret, (short) 0, SC_BLOCK_SIZE);
short len = scCipher.doFinal(apduBuffer, (short)(ISO7816.OFFSET_CDATA + SC_BLOCK_SIZE), (short) (apduLen - SC_BLOCK_SIZE), apduBuffer, ISO7816.OFFSET_CDATA);
short len = crypto.aesCbcIso9797m2.doFinal(apduBuffer, (short)(ISO7816.OFFSET_CDATA + SC_BLOCK_SIZE), (short) (apduLen - SC_BLOCK_SIZE), apduBuffer, ISO7816.OFFSET_CDATA);
apduBuffer[ISO7816.OFFSET_LC] = (byte) len;
@ -340,8 +337,8 @@ public class SecureChannel {
Util.setShort(apduBuffer, (short) (SC_OUT_OFFSET + len), sw);
len += 2;
scCipher.init(scEncKey, Cipher.MODE_ENCRYPT, secret, (short) 0, SC_BLOCK_SIZE);
len = scCipher.doFinal(apduBuffer, SC_OUT_OFFSET, len, apduBuffer, (short)(ISO7816.OFFSET_CDATA + SC_BLOCK_SIZE));
crypto.aesCbcIso9797m2.init(scEncKey, Cipher.MODE_ENCRYPT, secret, (short) 0, SC_BLOCK_SIZE);
len = crypto.aesCbcIso9797m2.doFinal(apduBuffer, SC_OUT_OFFSET, len, apduBuffer, (short)(ISO7816.OFFSET_CDATA + SC_BLOCK_SIZE));
apduBuffer[0] = (byte) (len + SC_BLOCK_SIZE);

View File

@ -2,6 +2,7 @@ package im.status.wallet;
import javacard.framework.*;
import javacard.security.*;
import javacardx.crypto.Cipher;
/**
* The applet's main class. All incoming commands a processed by this class.
@ -592,13 +593,10 @@ public class WalletApplet extends Applet {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
boolean newExtended = false;
switch (apduBuffer[ISO7816.OFFSET_P1]) {
case LOAD_KEY_P1_EXT_EC:
newExtended = true;
case LOAD_KEY_P1_EC:
loadKeyPair(apduBuffer, newExtended);
case LOAD_KEY_P1_EXT_EC:
loadKeyPair(apduBuffer);
break;
case LOAD_KEY_P1_SEED:
loadSeed(apduBuffer);
@ -635,13 +633,11 @@ public class WalletApplet extends Applet {
/**
* Called internally by the loadKey method to load a key in the TLV format. The presence of the public key is optional.
* The presence of a chain code is indicated explicitly through the newExtended argument (which is set depending on
* the P1 parameter of the command).
* The presence of the chain code determines whether the key is extended or not.
*
* @param apduBuffer the APDU buffer
* @param newExtended whether the key to load contains a chain code or not
*/
private void loadKeyPair(byte[] apduBuffer, boolean newExtended) {
private void loadKeyPair(byte[] apduBuffer) {
short pubOffset = (short)(ISO7816.OFFSET_CDATA + (apduBuffer[(short) (ISO7816.OFFSET_CDATA + 1)] == (byte) 0x81 ? 3 : 2));
short privOffset = (short)(pubOffset + apduBuffer[(short)(pubOffset + 1)] + 2);
short chainOffset = (short)(privOffset + apduBuffer[(short)(privOffset + 1)] + 2);
@ -652,14 +648,14 @@ public class WalletApplet extends Applet {
pubOffset = -1;
}
if (!((apduBuffer[ISO7816.OFFSET_CDATA] == TLV_KEY_TEMPLATE) && (apduBuffer[privOffset] == TLV_PRIV_KEY) && (!newExtended || apduBuffer[chainOffset] == TLV_CHAIN_CODE))) {
if (!((apduBuffer[ISO7816.OFFSET_CDATA] == TLV_KEY_TEMPLATE) && (apduBuffer[privOffset] == TLV_PRIV_KEY))) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
JCSystem.beginTransaction();
try {
isExtended = newExtended;
isExtended = (apduBuffer[chainOffset] == TLV_CHAIN_CODE);
masterPrivate.setS(apduBuffer, (short) (privOffset + 2), apduBuffer[(short) (privOffset + 1)]);
privateKey.setS(apduBuffer, (short) (privOffset + 2), apduBuffer[(short) (privOffset + 1)]);
@ -1043,19 +1039,40 @@ public class WalletApplet extends Applet {
private short exportDuplicate(byte[] apduBuffer) {
finalizeDuplicationKey();
return 0;
crypto.random.generateData(apduBuffer, SecureChannel.SC_OUT_OFFSET, Crypto.AES_BLOCK_SIZE);
short off = (short) (SecureChannel.SC_OUT_OFFSET + Crypto.AES_BLOCK_SIZE);
Util.arrayCopyNonAtomic(apduBuffer, SecureChannel.SC_OUT_OFFSET, apduBuffer, off, Crypto.AES_BLOCK_SIZE);
off += Crypto.AES_BLOCK_SIZE;
apduBuffer[off++] = TLV_KEY_TEMPLATE;
short keyTemplateLenOff = off++;
apduBuffer[off++] = TLV_PRIV_KEY;
apduBuffer[off] = (byte) masterPrivate.getS(apduBuffer, (short) (off + 1));
apduBuffer[keyTemplateLenOff] = (byte) (apduBuffer[off] + 2);
off += (short) (apduBuffer[off] + 1);
if (isExtended) {
apduBuffer[off++] = TLV_CHAIN_CODE;
apduBuffer[off++] = CHAIN_CODE_SIZE;
Util.arrayCopyNonAtomic(masterChainCode, (short) 0, apduBuffer, off, CHAIN_CODE_SIZE);
apduBuffer[keyTemplateLenOff] += (byte) (CHAIN_CODE_SIZE + 2);
off += CHAIN_CODE_SIZE;
}
return (short) (Crypto.AES_BLOCK_SIZE + crypto.oneShotAES(Cipher.MODE_ENCRYPT, apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + Crypto.AES_BLOCK_SIZE), off, apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + Crypto.AES_BLOCK_SIZE), duplicationEncKey, (short) 0));
}
private void importDuplicate(byte[] apduBuffer) {
finalizeDuplicationKey();
short len = crypto.oneShotAES(Cipher.MODE_DECRYPT, apduBuffer, ISO7816.OFFSET_CDATA, (short) (apduBuffer[ISO7816.OFFSET_LC] & 0xff), apduBuffer, ISO7816.OFFSET_CDATA, duplicationEncKey, (short) 0);
apduBuffer[ISO7816.OFFSET_LC] = (byte) len;
loadKeyPair(apduBuffer);
}
/**
* Processes the SIGN command. Requires a secure channel to open and either the PIN to be verified or the PIN-less key
* path to be the current key path. This command supports signing data using SHA-256 with possible segmentation over
* multiple APDUs as well as signing a precomputed 32-bytes hash. The latter option is the actual use case at the
* moment, since Ethereum signatures actually require Keccak-256 hashes, which are not supported by any version of
* JavaCard (including 3.0.5 which supports SHA-3 but not Keccak-256 which is slightly different). The signature is
* path to be the current key path. This command supports signing a precomputed 32-bytes hash. The signature is
* generated using the current keys, so if no keys are loaded the command does not work. The result of the execution
* is not the plain signature, but a TLV object containing the public key which must be used to verify the signature
* and the signature itself. The client should use this to calculate 'v' and format the signature according to the