implement DERIVE KEY test
This commit is contained in:
parent
de91e33f74
commit
32fbcfdcd5
|
@ -157,7 +157,7 @@ signing sessions, if any. Unless a DERIVE KEY is sent, a subsequent SIGN command
|
||||||
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid
|
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid
|
||||||
* Preconditions: Secure Channel must be opened, user PIN must be verified, an extended keyset must be loaded
|
* Preconditions: Secure Channel must be opened, user PIN must be verified, an extended keyset must be loaded
|
||||||
|
|
||||||
This command is used before a signing session to generated a private key according to the [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
|
This command is used before a signing session to generate a private key according to the [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
|
||||||
specifications. The generated key is used for all subsequent SIGN sessions.
|
specifications. The generated key is used for all subsequent SIGN sessions.
|
||||||
|
|
||||||
### GENERATE MNEMONIC
|
### GENERATE MNEMONIC
|
||||||
|
|
|
@ -34,6 +34,7 @@ repositories {
|
||||||
dependencies {
|
dependencies {
|
||||||
testCompile(files("../jcardsim/jcardsim-3.0.5-SNAPSHOT.jar"))
|
testCompile(files("../jcardsim/jcardsim-3.0.5-SNAPSHOT.jar"))
|
||||||
testCompile('org.web3j:core:2.3.1')
|
testCompile('org.web3j:core:2.3.1')
|
||||||
|
testCompile('org.bitcoinj:bitcoinj-core:0.14.5')
|
||||||
testCompile("org.bouncycastle:bcprov-jdk15on:1.58")
|
testCompile("org.bouncycastle:bcprov-jdk15on:1.58")
|
||||||
testCompile("org.junit.jupiter:junit-jupiter-api:5.0.0")
|
testCompile("org.junit.jupiter:junit-jupiter-api:5.0.0")
|
||||||
testRuntime("org.junit.jupiter:junit-jupiter-engine:5.0.0")
|
testRuntime("org.junit.jupiter:junit-jupiter-engine:5.0.0")
|
||||||
|
|
|
@ -343,7 +343,38 @@ public class WalletApplet extends Applet {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deriveKey(APDU apdu) {
|
private void deriveKey(APDU apdu) {
|
||||||
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
|
apdu.setIncomingAndReceive();
|
||||||
|
|
||||||
|
if (!(secureChannel.isOpen() && pin.isValidated() && isExtended)) {
|
||||||
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] apduBuffer = apdu.getBuffer();
|
||||||
|
|
||||||
|
short len = secureChannel.decryptAPDU(apduBuffer);
|
||||||
|
|
||||||
|
if ((short) (len % 4) != 0) {
|
||||||
|
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetKeys(apduBuffer, len);
|
||||||
|
|
||||||
|
for (short i = 0; i < len; i += 4) {
|
||||||
|
Crypto.bip32CKDPriv(apduBuffer, i, privateKey, publicKey, chainCode, (short) 0);
|
||||||
|
short pubLen = SECP256k1.derivePublicKey(privateKey, apduBuffer, (short) 0);
|
||||||
|
publicKey.setW(apduBuffer, (short) 0, pubLen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetKeys(byte[] buffer, short offset) {
|
||||||
|
short pubOff = (short) (offset + masterPrivate.getS(buffer, offset));
|
||||||
|
short pubLen = masterPublic.getW(buffer, pubOff);
|
||||||
|
|
||||||
|
JCSystem.beginTransaction();
|
||||||
|
Util.arrayCopy(masterChainCode, (short) 0, chainCode, (short) 0, CHAIN_CODE_SIZE);
|
||||||
|
privateKey.setS(buffer, offset, CHAIN_CODE_SIZE);
|
||||||
|
publicKey.setW(buffer, pubOff, pubLen);
|
||||||
|
JCSystem.commitTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateMnemonic(APDU apdu) {
|
private void generateMnemonic(APDU apdu) {
|
||||||
|
|
|
@ -176,4 +176,9 @@ public class WalletAppletCommandSet {
|
||||||
CommandAPDU sign = new CommandAPDU(0x80, WalletApplet.INS_SIGN, dataType, p2, secureChannel.encryptAPDU(data));
|
CommandAPDU sign = new CommandAPDU(0x80, WalletApplet.INS_SIGN, dataType, p2, secureChannel.encryptAPDU(data));
|
||||||
return apduChannel.transmit(sign);
|
return apduChannel.transmit(sign);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResponseAPDU deriveKey(byte[] data) throws CardException {
|
||||||
|
CommandAPDU deriveKey = new CommandAPDU(0x80, WalletApplet.INS_DERIVE_KEY, 0x00, 0x00, secureChannel.encryptAPDU(data));
|
||||||
|
return apduChannel.transmit(deriveKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@ import com.licel.jcardsim.smartcardio.CardSimulator;
|
||||||
import com.licel.jcardsim.smartcardio.CardTerminalSimulator;
|
import com.licel.jcardsim.smartcardio.CardTerminalSimulator;
|
||||||
import com.licel.jcardsim.utils.AIDUtil;
|
import com.licel.jcardsim.utils.AIDUtil;
|
||||||
import javacard.framework.AID;
|
import javacard.framework.AID;
|
||||||
|
import org.bitcoinj.crypto.ChildNumber;
|
||||||
|
import org.bitcoinj.crypto.DeterministicKey;
|
||||||
|
import org.bitcoinj.crypto.HDKeyDerivation;
|
||||||
import org.bouncycastle.jce.ECNamedCurveTable;
|
import org.bouncycastle.jce.ECNamedCurveTable;
|
||||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
@ -329,7 +332,7 @@ public class WalletAppletTest {
|
||||||
@DisplayName("GENERATE MNEMONIC command")
|
@DisplayName("GENERATE MNEMONIC command")
|
||||||
void generateMnemonicTest() throws Exception {
|
void generateMnemonicTest() throws Exception {
|
||||||
// Security condition violation: SecureChannel not open
|
// Security condition violation: SecureChannel not open
|
||||||
ResponseAPDU response = cmdSet.getStatus();
|
ResponseAPDU response = cmdSet.generateMnemonic(4);
|
||||||
assertEquals(0x6985, response.getSW());
|
assertEquals(0x6985, response.getSW());
|
||||||
cmdSet.openSecureChannel();
|
cmdSet.openSecureChannel();
|
||||||
|
|
||||||
|
@ -362,6 +365,46 @@ public class WalletAppletTest {
|
||||||
assertMnemonic(24, secureChannel.decryptAPDU(response.getData()));
|
assertMnemonic(24, secureChannel.decryptAPDU(response.getData()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("DERIVE KEY command")
|
||||||
|
void deriveKeyTest() throws Exception {
|
||||||
|
// Security condition violation: SecureChannel not open
|
||||||
|
ResponseAPDU response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertEquals(0x6985, response.getSW());
|
||||||
|
|
||||||
|
cmdSet.openSecureChannel();
|
||||||
|
|
||||||
|
// Security condition violation: PIN is not verified
|
||||||
|
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertEquals(0x6985, response.getSW());
|
||||||
|
|
||||||
|
response = cmdSet.verifyPIN("000000");
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
|
||||||
|
KeyPairGenerator g = keypairGenerator();
|
||||||
|
KeyPair keyPair = g.generateKeyPair();
|
||||||
|
byte[] chainCode = new byte[32];
|
||||||
|
new Random().nextBytes(chainCode);
|
||||||
|
|
||||||
|
// Condition violation: keyset is not extended
|
||||||
|
response = cmdSet.loadKey(keyPair);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertEquals(0x6985, response.getSW());
|
||||||
|
|
||||||
|
response = cmdSet.loadKey(keyPair, false, chainCode);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
|
||||||
|
// Wrong data format (data length not a multiple of 4)
|
||||||
|
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00});
|
||||||
|
assertEquals(0x6A80, response.getSW());
|
||||||
|
|
||||||
|
// Correct example
|
||||||
|
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
verifyKeyDerivation(keyPair, chainCode, new int[] { 1 });
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("SIGN command")
|
@DisplayName("SIGN command")
|
||||||
void signTest() throws Exception {
|
void signTest() throws Exception {
|
||||||
|
@ -590,6 +633,24 @@ public class WalletAppletTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void verifyKeyDerivation(KeyPair keyPair, byte[] chainCode, int[] path) throws Exception {
|
||||||
|
DeterministicKey key = HDKeyDerivation.createMasterPrivKeyFromBytes(((org.bouncycastle.jce.interfaces.ECPrivateKey) keyPair.getPrivate()).getD().toByteArray(), chainCode);
|
||||||
|
|
||||||
|
for (int i : path) {
|
||||||
|
key = HDKeyDerivation.deriveChildKey(key, new ChildNumber(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] hash = Hash.sha3(new byte[8]);
|
||||||
|
ResponseAPDU resp = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH, true, true);
|
||||||
|
assertEquals(0x9000, resp.getSW());
|
||||||
|
byte[] sig = secureChannel.decryptAPDU(resp.getData());
|
||||||
|
byte[] publicKey = extractPublicKey(sig);
|
||||||
|
sig = extractSignature(sig);
|
||||||
|
|
||||||
|
assertTrue(key.verify(hash, sig));
|
||||||
|
assertArrayEquals(key.getPubKeyPoint().getEncoded(false), publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
private Sign.SignatureData signMessage(byte[] message) throws Exception {
|
private Sign.SignatureData signMessage(byte[] message) throws Exception {
|
||||||
byte[] messageHash = Hash.sha3(message);
|
byte[] messageHash = Hash.sha3(message);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue