implement assisted DERIVE KEY

This commit is contained in:
Michele Balistreri 2017-10-24 15:42:59 +03:00
parent 49c8f06c2d
commit 760f431a16
2 changed files with 90 additions and 6 deletions

View File

@ -58,7 +58,7 @@ public class WalletApplet extends Applet {
static final byte TLV_KEY_INITIALIZATION_STATUS = (byte) 0xC2;
static final byte TLV_PUBLIC_KEY_DERIVATION = (byte) 0xC3;
private static final byte[] ASSISTED_DERIVATION_HASH = { (byte) 0xAA, (byte) 0x2D, (byte) 0xA9, (byte) 0x9D, (byte) 0x91, (byte) 0x8C, (byte) 0x7D, (byte) 0x95, (byte) 0xB8, (byte) 0x96, (byte) 0x89, (byte) 0x87, (byte) 0x3E, (byte) 0xAA, (byte) 0x37, (byte) 0x67, (byte) 0x25, (byte) 0x0C, (byte) 0xFF, (byte) 0x50, (byte) 0x13, (byte) 0x9A, (byte) 0x2F, (byte) 0x87, (byte) 0xBB, (byte) 0x4F, (byte) 0xCA, (byte) 0xB4, (byte) 0xAE, (byte) 0xC3, (byte) 0xE8, (byte) 0x90};
static final byte[] ASSISTED_DERIVATION_HASH = { (byte) 0xAA, (byte) 0x2D, (byte) 0xA9, (byte) 0x9D, (byte) 0x91, (byte) 0x8C, (byte) 0x7D, (byte) 0x95, (byte) 0xB8, (byte) 0x96, (byte) 0x89, (byte) 0x87, (byte) 0x3E, (byte) 0xAA, (byte) 0x37, (byte) 0x67, (byte) 0x25, (byte) 0x0C, (byte) 0xFF, (byte) 0x50, (byte) 0x13, (byte) 0x9A, (byte) 0x2F, (byte) 0x87, (byte) 0xBB, (byte) 0x4F, (byte) 0xCA, (byte) 0xB4, (byte) 0xAE, (byte) 0xC3, (byte) 0xE8, (byte) 0x90};
private OwnerPIN pin;
private OwnerPIN puk;
@ -367,8 +367,20 @@ public class WalletApplet extends Applet {
short len = secureChannel.decryptAPDU(apduBuffer);
boolean assistedDerivation = (apduBuffer[ISO7816.OFFSET_P1] & DERIVE_P1_ASSISTED_MASK) == DERIVE_P1_ASSISTED_MASK;
boolean isPublicKey = apduBuffer[ISO7816.OFFSET_P2] == DERIVE_P2_PUBLIC_KEY;
boolean isReset = (apduBuffer[ISO7816.OFFSET_P1] & DERIVE_P1_APPEND_MASK) != DERIVE_P1_APPEND_MASK;
if (((short) (len % 4) != 0) || (assistedDerivation && len > 0)) {
if ((isPublicKey != (expectPublicKey && !isReset)) || (isPublicKey && !assistedDerivation)) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
}
if (isPublicKey) {
publicKey.setW(apduBuffer, ISO7816.OFFSET_CDATA, len);
expectPublicKey = false;
return;
}
if (((short) (len % 4) != 0) || (assistedDerivation && (len > 4))) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
@ -378,7 +390,7 @@ public class WalletApplet extends Applet {
short chainEnd = (short) (ISO7816.OFFSET_CDATA + len);
if ((apduBuffer[ISO7816.OFFSET_P1] & DERIVE_P1_APPEND_MASK) != DERIVE_P1_APPEND_MASK) {
if (isReset) {
resetKeys(apduBuffer, chainEnd);
expectPublicKey = false;
}
@ -387,9 +399,33 @@ public class WalletApplet extends Applet {
for (short i = ISO7816.OFFSET_CDATA; i < chainEnd; i += 4) {
Crypto.bip32CKDPriv(apduBuffer, i, privateKey, publicKey, chainCode, (short) 0);
short pubLen = SECP256k1.derivePublicKey(privateKey, apduBuffer, chainEnd);
publicKey.setW(apduBuffer, chainEnd, pubLen);
if (assistedDerivation) {
expectPublicKey = true;
outputPublicX(apdu, apduBuffer);
return;
} else {
short pubLen = SECP256k1.derivePublicKey(privateKey, apduBuffer, chainEnd);
publicKey.setW(apduBuffer, chainEnd, pubLen);
}
}
expectPublicKey = false;
}
private void outputPublicX(APDU apdu, byte[] apduBuffer) {
short xLen = SECP256k1.derivePublicX(privateKey, apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + 4));
signature.init(privateKey, Signature.MODE_SIGN);
short sigLen = signature.signPreComputedHash(ASSISTED_DERIVATION_HASH, (short) 0, (short) ASSISTED_DERIVATION_HASH.length, apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + xLen + 4));
apduBuffer[SecureChannel.SC_OUT_OFFSET] = TLV_KEY_DERIVATION_TEMPLATE;
apduBuffer[(short) (SecureChannel.SC_OUT_OFFSET + 1)] = (byte) (xLen + sigLen + 2);
apduBuffer[(short) (SecureChannel.SC_OUT_OFFSET + 2)] = TLV_PUB_X;
apduBuffer[(short) (SecureChannel.SC_OUT_OFFSET + 3)] = (byte) xLen;
short outLen = secureChannel.encryptAPDU(apduBuffer, (short) (xLen + sigLen + 4));
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, outLen);
}
private void resetKeys(byte[] buffer, short offset) {

View File

@ -4,6 +4,7 @@ import com.licel.jcardsim.smartcardio.CardSimulator;
import com.licel.jcardsim.smartcardio.CardTerminalSimulator;
import com.licel.jcardsim.utils.AIDUtil;
import javacard.framework.AID;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.HDKeyDerivation;
@ -395,9 +396,19 @@ public class WalletAppletTest {
response = cmdSet.loadKey(keyPair, false, chainCode);
assertEquals(0x9000, response.getSW());
// Wrong data format (data length not a multiple of 4)
// Wrong P1/P2
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, false, true, true);
assertEquals(0x6A86, response.getSW());
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, false, false, true);
assertEquals(0x6A86, response.getSW());
// Wrong data format
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00});
assertEquals(0x6A80, response.getSW());
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00});
assertEquals(0x6A80, response.getSW());
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, true, true, false);
assertEquals(0x6A80, response.getSW());
// Correct
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01});
@ -422,6 +433,28 @@ public class WalletAppletTest {
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, false, false, false);
assertEquals(0x9000, response.getSW());
verifyKeyDerivation(keyPair, chainCode, new int[] { 1, 0x80000000, 2});
// Assisted derivation
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, true, true, false);
assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true);
assertEquals(0x9000, response.getSW());
verifyKeyDerivation(keyPair, chainCode, new int[] { 1 });
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, false, true, false);
assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true);
assertEquals(0x9000, response.getSW());
verifyKeyDerivation(keyPair, chainCode, new int[] { 1, 2 });
// Try to derive two keys at once
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, false, true, false);
assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, false, true, false);
assertEquals(0x6a86, response.getSW());
response = cmdSet.deriveKey(new byte[0]);
assertEquals(0x9000, response.getSW());
verifyKeyDerivation(keyPair, chainCode, new int[0]);
}
@Test
@ -670,6 +703,21 @@ public class WalletAppletTest {
assertArrayEquals(key.getPubKeyPoint().getEncoded(false), publicKey);
}
private byte[] derivePublicKey(byte[] data) {
byte[] pubKey = Arrays.copyOfRange(data, 3, 4 + data[3]);
byte[] signature = Arrays.copyOfRange(data, 4 + data[3], data.length);
pubKey[0] = 0x02;
ECKey candidate = ECKey.fromPublicOnly(pubKey);
if (!candidate.verify(WalletApplet.ASSISTED_DERIVATION_HASH, signature)) {
pubKey[0] = 0x03;
candidate = ECKey.fromPublicOnly(pubKey);
assertTrue(candidate.verify(WalletApplet.ASSISTED_DERIVATION_HASH, signature));
}
return candidate.decompress().getPubKey();
}
private Sign.SignatureData signMessage(byte[] message) throws Exception {
byte[] messageHash = Hash.sha3(message);