avoid deriving public when not needed, commit only final result

This commit is contained in:
Michele Balistreri 2018-11-29 19:22:14 +03:00
parent 852a7d9f48
commit 863ac1d6cf
3 changed files with 167 additions and 111 deletions

View File

@ -12,8 +12,9 @@ import javacardx.crypto.Cipher;
public class Crypto { public class Crypto {
final static public short AES_BLOCK_SIZE = 16; final static public short AES_BLOCK_SIZE = 16;
final static private short KEY_SECRET_SIZE = 32; final static short KEY_SECRET_SIZE = 32;
final static private short KEY_DERIVATION_INPUT_SIZE = 37; final static short KEY_PUB_SIZE = 65;
final static short KEY_DERIVATION_SCRATCH_SIZE = 37;
final static private short HMAC_OUT_SIZE = 64; final static private short HMAC_OUT_SIZE = 64;
final static private byte[] MAX_S = { (byte) 0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x5D, (byte) 0x57, (byte) 0x6E, (byte) 0x73, (byte) 0x57, (byte) 0xA4, (byte) 0x50, (byte) 0x1D, (byte) 0xDF, (byte) 0xE9, (byte) 0x2F, (byte) 0x46, (byte) 0x68, (byte) 0x1B, (byte) 0x20, (byte) 0xA0 }; final static private byte[] MAX_S = { (byte) 0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x5D, (byte) 0x57, (byte) 0x6E, (byte) 0x73, (byte) 0x57, (byte) 0xA4, (byte) 0x50, (byte) 0x1D, (byte) 0xDF, (byte) 0xE9, (byte) 0x2F, (byte) 0x46, (byte) 0x68, (byte) 0x1B, (byte) 0x20, (byte) 0xA0 };
@ -22,7 +23,6 @@ public class Crypto {
final static private byte HMAC_IPAD = (byte) 0x36; final static private byte HMAC_IPAD = (byte) 0x36;
final static private byte HMAC_OPAD = (byte) 0x5c; final static private byte HMAC_OPAD = (byte) 0x5c;
final static private short HMAC_BLOCK_SIZE = (short) 128; final static private short HMAC_BLOCK_SIZE = (short) 128;
final static private short HMAC_BLOCK_OFFSET = (short) KEY_DERIVATION_INPUT_SIZE + HMAC_OUT_SIZE;
final static private byte[] KEY_BITCOIN_SEED = {'B', 'i', 't', 'c', 'o', 'i', 'n', ' ', 's', 'e', 'e', 'd'}; final static private byte[] KEY_BITCOIN_SEED = {'B', 'i', 't', 'c', 'o', 'i', 'n', ' ', 's', 'e', 'e', 'd'};
@ -38,7 +38,7 @@ public class Crypto {
private AESKey tmpAES256; private AESKey tmpAES256;
private byte[] tmp; private byte[] hmacBlock;
Crypto() { Crypto() {
random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
@ -49,18 +49,14 @@ public class Crypto {
tmpAES256 = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_AES_256, false); tmpAES256 = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_AES_256, false);
short blockSize;
try { try {
blockSize = 0;
hmacSHA512 = Signature.getInstance(Signature.ALG_HMAC_SHA_512, false); hmacSHA512 = Signature.getInstance(Signature.ALG_HMAC_SHA_512, false);
hmacKey = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_DESELECT, KEY_SECRET_SIZE, false); hmacKey = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_DESELECT, KEY_SECRET_SIZE, false);
} catch (CryptoException e) { } catch (CryptoException e) {
hmacSHA512 = null; hmacSHA512 = null;
blockSize = HMAC_BLOCK_SIZE; hmacBlock = JCSystem.makeTransientByteArray(HMAC_BLOCK_SIZE, JCSystem.CLEAR_ON_RESET);
} }
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) { public short oneShotAES(byte mode, byte[] src, short sOff, short sLen, byte[] dst, short dOff, byte[] key, short keyOff) {
@ -69,6 +65,10 @@ public class Crypto {
return aesCbcIso9797m2.doFinal(src, (short) (sOff + AES_BLOCK_SIZE), sLen, dst, dOff); return aesCbcIso9797m2.doFinal(src, (short) (sOff + AES_BLOCK_SIZE), sLen, dst, dOff);
} }
boolean bip32IsHardened(byte[] i, short iOff) {
return (i[iOff] & (byte) 0x80) == (byte) 0x80;
}
/** /**
* Derives a private key according to the algorithm defined in BIP32. The BIP32 specifications define some checks * 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 * to be performed on the derived keys. In the very unlikely event that these checks fail this key is not considered
@ -76,43 +76,33 @@ public class Crypto {
* *
* @param i the buffer containing the key path element (a 32-bit big endian integer) * @param i the buffer containing the key path element (a 32-bit big endian integer)
* @param iOff the offset in the buffer * @param iOff the offset in the buffer
* @param privateKey the parent private key
* @param publicKey the parent public key
* @param chain the chain code
* @param chainOff the offset in the chain code buffer
* @return true if successful, false otherwise * @return true if successful, false otherwise
*/ */
boolean bip32CKDPriv(byte[] i, short iOff, ECPrivateKey privateKey, ECPublicKey publicKey, byte[] chain, short chainOff) { boolean bip32CKDPriv(byte[] i, short iOff, byte[] scratch, short scratchOff, byte[] data, short dataOff, byte[] output, short outOff) {
short off = 0; short off = scratchOff;
if ((i[iOff] & (byte) 0x80) == (byte) 0x80) { if (bip32IsHardened(i, iOff)) {
tmp[off++] = 0; scratch[off++] = 0;
off += privateKey.getS(tmp, off); off = Util.arrayCopyNonAtomic(data, dataOff, scratch, off, KEY_SECRET_SIZE);
} else { } else {
off = (short) (publicKey.getW(tmp, (short) 0) - 1); scratch[off++] = ((data[(short) (dataOff + KEY_SECRET_SIZE + KEY_SECRET_SIZE + KEY_PUB_SIZE - 1)] & 1) != 0 ? (byte) 0x03 : (byte) 0x02);
tmp[0] = ((tmp[off] & 1) != 0 ? (byte) 0x03 : (byte) 0x02); off = Util.arrayCopyNonAtomic(data, (short) (dataOff + KEY_SECRET_SIZE + KEY_SECRET_SIZE + 1), scratch, off, KEY_SECRET_SIZE);
off = (short) ((short) (off / 2) + 1);
} }
off = Util.arrayCopyNonAtomic(i, iOff, tmp, off, (short) 4); off = Util.arrayCopyNonAtomic(i, iOff, hmacBlock, off, (short) 4);
hmacSHA512(chain, chainOff, KEY_SECRET_SIZE, tmp, (short) 0, off, tmp, off); hmacSHA512(data, (short)(dataOff + KEY_SECRET_SIZE), KEY_SECRET_SIZE, scratch, scratchOff, off, output, outOff);
if (ucmp256(tmp, off, SECP256k1.SECP256K1_R, (short) 0) >= 0) { if (ucmp256(output, outOff, SECP256k1.SECP256K1_R, (short) 0) >= 0) {
return false; return false;
} }
privateKey.getS(tmp, (short) 0); addm256(output, outOff, data, dataOff, SECP256k1.SECP256K1_R, (short) 0, output, outOff);
addm256(tmp, off, tmp, (short) 0, SECP256k1.SECP256K1_R, (short) 0, tmp, off); if (isZero256(output, outOff)) {
if (isZero256(tmp, off)) {
return false; return false;
} }
privateKey.setS(tmp, off, (short) KEY_SECRET_SIZE);
Util.arrayCopy(tmp, (short)(off + KEY_SECRET_SIZE), chain, chainOff, (short) KEY_SECRET_SIZE);
return true; return true;
} }
@ -177,13 +167,13 @@ public class Crypto {
hmacSHA512.sign(in, inOff, inLen, out, outOff); hmacSHA512.sign(in, inOff, inLen, out, outOff);
} else { } else {
for (byte i = 0; i < 2; i++) { for (byte i = 0; i < 2; i++) {
Util.arrayFillNonAtomic(tmp, HMAC_BLOCK_OFFSET, HMAC_BLOCK_SIZE, (i == 0 ? HMAC_IPAD : HMAC_OPAD)); Util.arrayFillNonAtomic(hmacBlock, (short) 0, HMAC_BLOCK_SIZE, (i == 0 ? HMAC_IPAD : HMAC_OPAD));
for (short j = 0; j < keyLen; j++) { for (short j = 0; j < keyLen; j++) {
tmp[(short)(HMAC_BLOCK_OFFSET + j)] ^= key[(short)(keyOff + j)]; hmacBlock[j] ^= key[(short)(keyOff + j)];
} }
sha512.update(tmp, HMAC_BLOCK_OFFSET, HMAC_BLOCK_SIZE); sha512.update(hmacBlock, (short) 0, HMAC_BLOCK_SIZE);
if (i == 0) { if (i == 0) {
sha512.doFinal(in, inOff, inLen, out, outOff); sha512.doFinal(in, inOff, inLen, out, outOff);

View File

@ -3,6 +3,7 @@ package im.status.wallet;
import javacard.security.ECKey; import javacard.security.ECKey;
import javacard.security.ECPrivateKey; import javacard.security.ECPrivateKey;
import javacard.security.KeyAgreement; import javacard.security.KeyAgreement;
import javacard.security.KeyBuilder;
/** /**
* Utility methods to work with the SECP256k1 curve. This class is not meant to be instantiated, but its init method * Utility methods to work with the SECP256k1 curve. This class is not meant to be instantiated, but its init method
@ -47,17 +48,22 @@ public class SECP256k1 {
static final byte SECP256K1_K = (byte)0x01; static final byte SECP256K1_K = (byte)0x01;
static final short SECP256K1_KEY_SIZE = 256;
private static final byte ALG_EC_SVDP_DH_PLAIN_XY = 6; // constant from JavaCard 3.0.5 private static final byte ALG_EC_SVDP_DH_PLAIN_XY = 6; // constant from JavaCard 3.0.5
private KeyAgreement ecPointMultiplier; private KeyAgreement ecPointMultiplier;
private Crypto crypto; private Crypto crypto;
private ECPrivateKey tmpECPrivateKey;
/** /**
* Allocates objects needed by this class. Must be invoked during the applet installation exactly 1 time. * Allocates objects needed by this class. Must be invoked during the applet installation exactly 1 time.
*/ */
SECP256k1(Crypto crypto) { SECP256k1(Crypto crypto) {
this.crypto = crypto; this.crypto = crypto;
ecPointMultiplier = KeyAgreement.getInstance(ALG_EC_SVDP_DH_PLAIN_XY, false); this.ecPointMultiplier = KeyAgreement.getInstance(ALG_EC_SVDP_DH_PLAIN_XY, false);
this.tmpECPrivateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256K1_KEY_SIZE, false);
} }
/** /**
@ -87,6 +93,24 @@ public class SECP256k1 {
return multiplyPoint(privateKey, SECP256K1_G, (short) 0, (short) SECP256K1_G.length, pubOut, pubOff); return multiplyPoint(privateKey, SECP256K1_G, (short) 0, (short) SECP256K1_G.length, pubOut, pubOff);
} }
/**
* Derives the public key from the given private key and outputs it in the pubOut buffer. This is done by multiplying
* the private key by the G point of the curve.
*
* @param privateKey the private key
* @param pubOut the output buffer for the public key
* @param pubOff the offset in pubOut
* @return the length of the public key
*/
short derivePublicKey(byte[] privateKey, short privOff, byte[] pubOut, short pubOff) {
tmpECPrivateKey.setS(privateKey, privOff, (short)(SECP256K1_KEY_SIZE/8));
short res = derivePublicKey(tmpECPrivateKey, pubOut, pubOff);
// Unfortunately our current card does not support EC transient keys
tmpECPrivateKey.clearKey();
return res;
}
/** /**
* Multiplies a scalar in the form of a private key by the given point. Internally uses a special version of EC-DH * Multiplies a scalar in the form of a private key by the given point. Internally uses a special version of EC-DH
* supported since JavaCard 3.0.5 which outputs both X and Y in their uncompressed form. * supported since JavaCard 3.0.5 which outputs both X and Y in their uncompressed form.

View File

@ -34,7 +34,6 @@ public class WalletApplet extends Applet {
static final byte PAIRING_MAX_CLIENT_COUNT = 5; static final byte PAIRING_MAX_CLIENT_COUNT = 5;
static final byte UID_LENGTH = 16; static final byte UID_LENGTH = 16;
static final short EC_KEY_SIZE = 256;
static final short CHAIN_CODE_SIZE = 32; static final short CHAIN_CODE_SIZE = 32;
static final short KEY_UID_LENGTH = 32; static final short KEY_UID_LENGTH = 32;
static final short BIP39_SEED_SIZE = CHAIN_CODE_SIZE * 2; static final short BIP39_SEED_SIZE = CHAIN_CODE_SIZE * 2;
@ -100,7 +99,6 @@ public class WalletApplet extends Applet {
private ECPublicKey parentPublicKey; private ECPublicKey parentPublicKey;
private ECPrivateKey parentPrivateKey; private ECPrivateKey parentPrivateKey;
private byte[] parentChainCode; private byte[] parentChainCode;
private boolean parentValid;
private ECPublicKey publicKey; private ECPublicKey publicKey;
private ECPrivateKey privateKey; private ECPrivateKey privateKey;
@ -152,14 +150,14 @@ public class WalletApplet extends Applet {
uid = new byte[UID_LENGTH]; uid = new byte[UID_LENGTH];
crypto.random.generateData(uid, (short) 0, UID_LENGTH); crypto.random.generateData(uid, (short) 0, UID_LENGTH);
masterPublic = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, EC_KEY_SIZE, false); masterPublic = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, SECP256k1.SECP256K1_KEY_SIZE, false);
masterPrivate = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, EC_KEY_SIZE, false); masterPrivate = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256k1.SECP256K1_KEY_SIZE, false);
parentPublicKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, EC_KEY_SIZE, false); parentPublicKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, SECP256k1.SECP256K1_KEY_SIZE, false);
parentPrivateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, EC_KEY_SIZE, false); parentPrivateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256k1.SECP256K1_KEY_SIZE, false);
publicKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, EC_KEY_SIZE, false); publicKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, SECP256k1.SECP256K1_KEY_SIZE, false);
privateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, EC_KEY_SIZE, false); privateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, SECP256k1.SECP256K1_KEY_SIZE, false);
masterChainCode = new byte[CHAIN_CODE_SIZE]; masterChainCode = new byte[CHAIN_CODE_SIZE];
parentChainCode = new byte[CHAIN_CODE_SIZE]; parentChainCode = new byte[CHAIN_CODE_SIZE];
@ -628,9 +626,10 @@ public class WalletApplet extends Applet {
* Resets the status of the keys. This method must be called immediately before committing the transaction where key * Resets the status of the keys. This method must be called immediately before committing the transaction where key
* manipulation has happened to be sure that the state is always consistent. * manipulation has happened to be sure that the state is always consistent.
*/ */
private void resetKeyStatus(boolean toParent) { private void resetKeyStatus() {
parentValid = false; parentPrivateKey.clearKey();
keyPathLen = toParent ? (short) (keyPathLen - 4) : 0; parentPublicKey.clearKey();
keyPathLen = 0;
} }
/** /**
@ -687,7 +686,7 @@ public class WalletApplet extends Applet {
ISOException.throwIt(ISO7816.SW_WRONG_DATA); ISOException.throwIt(ISO7816.SW_WRONG_DATA);
} }
resetKeyStatus(false); resetKeyStatus();
JCSystem.commitTransaction(); JCSystem.commitTransaction();
} }
@ -719,7 +718,7 @@ public class WalletApplet extends Applet {
masterPublic.setW(apduBuffer, (short) 0, pubLen); masterPublic.setW(apduBuffer, (short) 0, pubLen);
publicKey.setW(apduBuffer, (short) 0, pubLen); publicKey.setW(apduBuffer, (short) 0, pubLen);
resetKeyStatus(false); resetKeyStatus();
JCSystem.commitTransaction(); JCSystem.commitTransaction();
} }
@ -754,96 +753,140 @@ public class WalletApplet extends Applet {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
} }
boolean isReset = apduBuffer[ISO7816.OFFSET_P1] == DERIVE_P1_SOURCE_MASTER; doDerive(apduBuffer, len, apduBuffer[ISO7816.OFFSET_P1], true);
boolean fromParent = apduBuffer[ISO7816.OFFSET_P1] == DERIVE_P1_SOURCE_PARENT;
doDerive(apduBuffer, len, isReset, fromParent);
} }
/** /**
* Internal derivation function, called by DERIVE KEY and EXPORT KEY * Internal derivation function, called by DERIVE KEY and EXPORT KEY
* @param apduBuffer the APDU buffer * @param apduBuffer the APDU buffer
* @param len the APDU len * @param len the APDU len
* @param isReset start deriving from master * @param source derivation source
* @param fromParent start deriving from parent * @param makeCurrent whether the results should be saved or not
*/ */
private void doDerive(byte[] apduBuffer, short len, boolean isReset, boolean fromParent) { private void doDerive(byte[] apduBuffer, short len, byte source, boolean makeCurrent) {
if (!isExtended) { if (!isExtended) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
} }
if (fromParent && !parentValid) { short newPathLen;
short pathLenOff;
ECPublicKey sourcePub;
ECPrivateKey sourcePriv;
byte[] sourceChain;
switch (source) {
case DERIVE_P1_SOURCE_MASTER:
if (len == 0) {
resetToMaster(apduBuffer);
return;
}
newPathLen = len;
sourcePriv = masterPrivate;
sourcePub = masterPublic;
sourceChain = masterChainCode;
pathLenOff = 0;
break;
case DERIVE_P1_SOURCE_PARENT:
if (!parentPublicKey.isInitialized()) {
ISOException.throwIt(ISO7816.SW_WRONG_P1P2); ISOException.throwIt(ISO7816.SW_WRONG_P1P2);
} }
if (((short) (len % 4) != 0) || ((short)(len + (isReset ? 0 : keyPathLen)) > keyPath.length)) { if (len == 0) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA); ISOException.throwIt(ISO7816.SW_WRONG_DATA);
} }
short chainEnd = (short) (ISO7816.OFFSET_CDATA + len); newPathLen = (short) (keyPathLen + len - 4);
sourcePriv = parentPrivateKey;
if (isReset || fromParent) { sourcePub = parentPublicKey;
resetKeys(fromParent, apduBuffer, chainEnd); sourceChain = parentChainCode;
pathLenOff = (short) (keyPathLen - 4);
break;
case DERIVE_P1_SOURCE_CURRENT:
if (len == 0) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
} }
for (short i = ISO7816.OFFSET_CDATA; i < chainEnd; i += 4) { newPathLen = (short) (keyPathLen + len);
JCSystem.beginTransaction(); sourcePriv = privateKey;
sourcePub = publicKey;
sourceChain = chainCode;
pathLenOff = keyPathLen;
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
return;
}
copyKeys(privateKey, publicKey, chainCode, parentPrivateKey, parentPublicKey, parentChainCode, apduBuffer, chainEnd); if (((short) (len % 4) != 0) || (newPathLen > keyPath.length)) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
if (!crypto.bip32CKDPriv(apduBuffer, i, privateKey, publicKey, chainCode, (short) 0)) { short scratchOff = (short) (ISO7816.OFFSET_CDATA + len);
short dataOff = (short) (scratchOff + Crypto.KEY_DERIVATION_SCRATCH_SIZE);
short pubKeyOff = (short) (dataOff + sourcePriv.getS(apduBuffer, dataOff));
pubKeyOff = Util.arrayCopyNonAtomic(sourceChain, (short)0, apduBuffer, pubKeyOff, CHAIN_CODE_SIZE);
short outputOff = (short) (pubKeyOff + Crypto.KEY_PUB_SIZE);
if (!crypto.bip32IsHardened(apduBuffer, ISO7816.OFFSET_CDATA)) {
sourcePub.getW(apduBuffer, pubKeyOff);
} else {
apduBuffer[pubKeyOff] = 0;
}
for (short i = ISO7816.OFFSET_CDATA; i < scratchOff; i += 4) {
Util.arrayCopyNonAtomic(apduBuffer, outputOff, apduBuffer, dataOff, (short) (Crypto.KEY_SECRET_SIZE + CHAIN_CODE_SIZE));
if ((i > ISO7816.OFFSET_CDATA) && !crypto.bip32IsHardened(apduBuffer, i)) {
secp256k1.derivePublicKey(apduBuffer, dataOff, apduBuffer, pubKeyOff);
} else {
apduBuffer[pubKeyOff] = 0;
}
if (!crypto.bip32CKDPriv(apduBuffer, i, apduBuffer, scratchOff, apduBuffer, dataOff, apduBuffer, outputOff)) {
ISOException.throwIt(ISO7816.SW_DATA_INVALID); ISOException.throwIt(ISO7816.SW_DATA_INVALID);
} }
Util.arrayCopy(apduBuffer, i, keyPath, keyPathLen, (short) 4);
short pubLen = secp256k1.derivePublicKey(privateKey, apduBuffer, chainEnd);
publicKey.setW(apduBuffer, chainEnd, pubLen);
keyPathLen += 4;
parentValid = true;
JCSystem.commitTransaction();
}
} }
/** if (makeCurrent) {
* Resets the current key and key path to the parent or master key. A transaction is used to make sure this all
* happens at once. This method is called internally by the deriveKey method.
*
* @param toParent resets to the parent key
* @param buffer a buffer which can be overwritten (currently the APDU buffer)
* @param offset the offset at which the buffer is free
*/
private void resetKeys(boolean toParent, byte[] buffer, short offset) {
ECPrivateKey srcPrivKey = toParent ? parentPrivateKey : masterPrivate;
ECPublicKey srcPubKey = toParent ? parentPublicKey : masterPublic;
byte[] srcChainCode = toParent ? parentChainCode : masterChainCode;
JCSystem.beginTransaction(); JCSystem.beginTransaction();
copyKeys(srcPrivKey, srcPubKey, srcChainCode, privateKey, publicKey, chainCode, buffer, offset);
resetKeyStatus(toParent); parentPrivateKey.setS(apduBuffer, dataOff, Crypto.KEY_SECRET_SIZE);
Util.arrayCopy(apduBuffer, (short)(dataOff + Crypto.KEY_SECRET_SIZE), parentChainCode, (short) 0, CHAIN_CODE_SIZE);
if (apduBuffer[pubKeyOff] == 0x04) {
parentPublicKey.setW(apduBuffer, pubKeyOff, Crypto.KEY_PUB_SIZE);
} else {
secp256k1.derivePublicKey(parentPrivateKey, apduBuffer, scratchOff);
parentPublicKey.setW(apduBuffer, scratchOff, Crypto.KEY_PUB_SIZE);
}
parentPublicKey.setW(apduBuffer, pubKeyOff, Crypto.KEY_PUB_SIZE);
privateKey.setS(apduBuffer, outputOff, Crypto.KEY_SECRET_SIZE);
Util.arrayCopy(apduBuffer, (short)(outputOff + Crypto.KEY_SECRET_SIZE), chainCode, (short) 0, CHAIN_CODE_SIZE);
secp256k1.derivePublicKey(privateKey, apduBuffer, scratchOff);
publicKey.setW(apduBuffer, scratchOff, Crypto.KEY_PUB_SIZE);
Util.arrayCopy(apduBuffer, ISO7816.OFFSET_CDATA, keyPath, pathLenOff, len);
keyPathLen = newPathLen;
JCSystem.commitTransaction(); JCSystem.commitTransaction();
} }
}
/** /**
* Copys a key set to another one. Requires a transient buffer which can be overwritten. * Resets to master key
* *
* @param srcPrivate source private key * @param apduBuffer the APDU buffer
* @param srcPublic source public key
* @param srcChain source chain code
* @param dstPrivate destination private key
* @param dstPublic destination public key
* @param dstChain destination chain code
* @param buffer tmp buffer
* @param offset tmp buffer offset
*/ */
private void copyKeys(ECPrivateKey srcPrivate, ECPublicKey srcPublic, byte[] srcChain, ECPrivateKey dstPrivate, ECPublicKey dstPublic, byte[] dstChain, byte[] buffer, short offset) { private void resetToMaster(byte[] apduBuffer) {
short pubOff = (short) (offset + srcPrivate.getS(buffer, offset)); resetKeyStatus();
short pubLen = srcPublic.getW(buffer, pubOff); masterPrivate.getS(apduBuffer, ISO7816.OFFSET_CDATA);
privateKey.setS(apduBuffer, ISO7816.OFFSET_CDATA, Crypto.KEY_SECRET_SIZE);
Util.arrayCopy(srcChain, (short) 0, dstChain, (short) 0, CHAIN_CODE_SIZE); masterPublic.getW(apduBuffer, ISO7816.OFFSET_CDATA);
dstPrivate.setS(buffer, offset, CHAIN_CODE_SIZE); publicKey.setW(apduBuffer, ISO7816.OFFSET_CDATA, Crypto.KEY_PUB_SIZE);
dstPublic.setW(buffer, pubOff, pubLen); Util.arrayCopyNonAtomic(masterChainCode, (short) 0, chainCode, (short) 0, CHAIN_CODE_SIZE);
} }
/** /**
@ -941,7 +984,6 @@ public class WalletApplet extends Applet {
keyPathLen = 0; keyPathLen = 0;
pinlessPathLen = 0; pinlessPathLen = 0;
parentValid = false;
isExtended = false; isExtended = false;
privateKey.clearKey(); privateKey.clearKey();
publicKey.clearKey(); publicKey.clearKey();
@ -1209,8 +1251,8 @@ public class WalletApplet extends Applet {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
} }
if (makeCurrent) { if (derive) {
doDerive(apduBuffer, dataLen, true, false); doDerive(apduBuffer, dataLen, DERIVE_P1_SOURCE_MASTER, makeCurrent);
} }
short off = SecureChannel.SC_OUT_OFFSET; short off = SecureChannel.SC_OUT_OFFSET;