add web3j based test (incomplete)
This commit is contained in:
parent
7c72c16578
commit
fb24995cee
|
@ -33,11 +33,20 @@ repositories {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testCompile(files("../jcardsim/target/jcardsim-3.0.5-SNAPSHOT.jar"))
|
testCompile(files("../jcardsim/target/jcardsim-3.0.5-SNAPSHOT.jar"))
|
||||||
|
testCompile('org.web3j:core:2.3.1')
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
junitPlatform {
|
||||||
|
filters {
|
||||||
|
tags {
|
||||||
|
exclude 'manual'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sourceCompatibility = 1.3
|
sourceCompatibility = 1.3
|
||||||
targetCompatibility = 1.3
|
targetCompatibility = 1.3
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import javacard.framework.ISO7816;
|
||||||
import org.bouncycastle.jce.interfaces.ECPrivateKey;
|
import org.bouncycastle.jce.interfaces.ECPrivateKey;
|
||||||
import org.bouncycastle.jce.interfaces.ECPublicKey;
|
import org.bouncycastle.jce.interfaces.ECPublicKey;
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
import org.web3j.crypto.ECKeyPair;
|
||||||
|
|
||||||
import javax.smartcardio.CardChannel;
|
import javax.smartcardio.CardChannel;
|
||||||
import javax.smartcardio.CardException;
|
import javax.smartcardio.CardException;
|
||||||
|
@ -75,6 +76,40 @@ public class WalletAppletCommandSet {
|
||||||
return loadKey(data, WalletApplet.LOAD_KEY_EC);
|
return loadKey(data, WalletApplet.LOAD_KEY_EC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResponseAPDU loadKey(ECKeyPair ecKeyPair) throws CardException {
|
||||||
|
byte[] publicKey = ecKeyPair.getPublicKey().toByteArray();
|
||||||
|
byte[] privateKey = ecKeyPair.getPrivateKey().toByteArray();
|
||||||
|
|
||||||
|
int privLen = privateKey.length;
|
||||||
|
int privOff = 0;
|
||||||
|
|
||||||
|
int pubLen = publicKey.length;
|
||||||
|
int pubOff = 0;
|
||||||
|
|
||||||
|
if(privateKey[0] == 0x00) {
|
||||||
|
privOff++;
|
||||||
|
privLen--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(publicKey[0] == 0x00) {
|
||||||
|
pubOff++;
|
||||||
|
pubLen--;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] data = new byte[pubLen + privLen + 7];
|
||||||
|
data[0] = (byte) 0xA1;
|
||||||
|
data[1] = (byte) (pubLen + privLen + 5);
|
||||||
|
data[2] = (byte) 0x80;
|
||||||
|
data[3] = (byte) (pubLen + 1);
|
||||||
|
data[4] = (byte) 0x04;
|
||||||
|
System.arraycopy(publicKey, pubOff, data, 5, pubLen);
|
||||||
|
data[5 + pubLen] = (byte) 0x81;
|
||||||
|
data[6 + pubLen] = (byte) privLen;
|
||||||
|
System.arraycopy(privateKey, privOff, data, 7 + pubLen, privLen);
|
||||||
|
|
||||||
|
return loadKey(data, WalletApplet.LOAD_KEY_EC);
|
||||||
|
}
|
||||||
|
|
||||||
public ResponseAPDU loadKey(byte[] data, byte keyType) throws CardException {
|
public ResponseAPDU loadKey(byte[] data, byte keyType) throws CardException {
|
||||||
CommandAPDU loadKey = new CommandAPDU(0x80, WalletApplet.INS_LOAD_KEY, keyType, 0, secureChannel.encryptAPDU(data));
|
CommandAPDU loadKey = new CommandAPDU(0x80, WalletApplet.INS_LOAD_KEY, keyType, 0, secureChannel.encryptAPDU(data));
|
||||||
return apduChannel.transmit(loadKey);
|
return apduChannel.transmit(loadKey);
|
||||||
|
|
|
@ -8,31 +8,48 @@ 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;
|
||||||
import org.junit.jupiter.api.*;
|
import org.junit.jupiter.api.*;
|
||||||
|
import org.web3j.crypto.*;
|
||||||
|
import org.web3j.protocol.Web3j;
|
||||||
|
import org.web3j.protocol.core.DefaultBlockParameterName;
|
||||||
|
import org.web3j.protocol.core.methods.request.RawTransaction;
|
||||||
|
import org.web3j.protocol.core.methods.response.EthSendTransaction;
|
||||||
|
import org.web3j.protocol.http.HttpService;
|
||||||
|
import org.web3j.tx.Transfer;
|
||||||
|
import org.web3j.utils.Convert;
|
||||||
|
|
||||||
import javax.smartcardio.*;
|
import javax.smartcardio.*;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
@DisplayName("Test the Wallet Applet")
|
@DisplayName("Test the Wallet Applet")
|
||||||
public class WalletAppletTest {
|
public class WalletAppletTest {
|
||||||
private static CardTerminal cardTerminal;
|
private static CardTerminal cardTerminal;
|
||||||
private static CardChannel apduChannel;
|
private static CardChannel apduChannel;
|
||||||
|
private static CardSimulator simulator;
|
||||||
|
|
||||||
private SecureChannelSession secureChannel;
|
private SecureChannelSession secureChannel;
|
||||||
private WalletAppletCommandSet cmdSet;
|
private WalletAppletCommandSet cmdSet;
|
||||||
|
|
||||||
private static final boolean USE_SIMULATOR = true;
|
private static final boolean USE_SIMULATOR;
|
||||||
|
|
||||||
|
static {
|
||||||
|
USE_SIMULATOR = !System.getProperty("im.status.wallet.test.simulated", "false").equals("false");
|
||||||
|
}
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
static void initAll() throws CardException {
|
static void initAll() throws CardException {
|
||||||
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
|
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
|
||||||
|
|
||||||
if (USE_SIMULATOR) {
|
if (USE_SIMULATOR) {
|
||||||
CardSimulator simulator = new CardSimulator();
|
simulator = new CardSimulator();
|
||||||
AID appletAID = AIDUtil.create(WalletAppletCommandSet.APPLET_AID);
|
AID appletAID = AIDUtil.create(WalletAppletCommandSet.APPLET_AID);
|
||||||
byte[] instParams = Hex.decode("0F53746174757357616C6C657441707001000C313233343536373839303132");
|
byte[] instParams = Hex.decode("0F53746174757357616C6C657441707001000C313233343536373839303132");
|
||||||
simulator.installApplet(appletAID, WalletApplet.class, instParams, (short) 0, (byte) instParams.length);
|
simulator.installApplet(appletAID, WalletApplet.class, instParams, (short) 0, (byte) instParams.length);
|
||||||
|
@ -54,7 +71,7 @@ public class WalletAppletTest {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void init() throws CardException {
|
void init() throws CardException {
|
||||||
apduChannel.getCard().getATR();
|
reset();
|
||||||
cmdSet = new WalletAppletCommandSet(apduChannel);
|
cmdSet = new WalletAppletCommandSet(apduChannel);
|
||||||
byte[] keyData = cmdSet.select().getData();
|
byte[] keyData = cmdSet.select().getData();
|
||||||
secureChannel = new SecureChannelSession(keyData);
|
secureChannel = new SecureChannelSession(keyData);
|
||||||
|
@ -146,9 +163,7 @@ public class WalletAppletTest {
|
||||||
assertEquals(0x9000, response.getSW());
|
assertEquals(0x9000, response.getSW());
|
||||||
|
|
||||||
// Reset card and verify that the new PIN has really been set
|
// Reset card and verify that the new PIN has really been set
|
||||||
apduChannel.getCard().getATR();
|
resetAndSelectAndOpenSC();
|
||||||
cmdSet.select();
|
|
||||||
cmdSet.openSecureChannel();
|
|
||||||
|
|
||||||
response = cmdSet.verifyPIN("654321");
|
response = cmdSet.verifyPIN("654321");
|
||||||
assertEquals(0x9000, response.getSW());
|
assertEquals(0x9000, response.getSW());
|
||||||
|
@ -207,9 +222,7 @@ public class WalletAppletTest {
|
||||||
assertEquals(0x9000, response.getSW());
|
assertEquals(0x9000, response.getSW());
|
||||||
|
|
||||||
// Check that PIN has been changed and unblocked
|
// Check that PIN has been changed and unblocked
|
||||||
apduChannel.getCard().getATR();
|
resetAndSelectAndOpenSC();
|
||||||
cmdSet.select();
|
|
||||||
cmdSet.openSecureChannel();
|
|
||||||
|
|
||||||
response = cmdSet.verifyPIN("654321");
|
response = cmdSet.verifyPIN("654321");
|
||||||
assertEquals(0x9000, response.getSW());
|
assertEquals(0x9000, response.getSW());
|
||||||
|
@ -353,9 +366,7 @@ public class WalletAppletTest {
|
||||||
// Signing session is aborted on reselection
|
// Signing session is aborted on reselection
|
||||||
response = cmdSet.sign(data, WalletApplet.SIGN_DATA,true, false);
|
response = cmdSet.sign(data, WalletApplet.SIGN_DATA,true, false);
|
||||||
assertEquals(0x9000, response.getSW());
|
assertEquals(0x9000, response.getSW());
|
||||||
apduChannel.getCard().getATR();
|
resetAndSelectAndOpenSC();
|
||||||
cmdSet.select();
|
|
||||||
cmdSet.openSecureChannel();
|
|
||||||
response = cmdSet.verifyPIN("000000");
|
response = cmdSet.verifyPIN("000000");
|
||||||
assertEquals(0x9000, response.getSW());
|
assertEquals(0x9000, response.getSW());
|
||||||
response = cmdSet.sign(smallData, WalletApplet.SIGN_DATA,false, true);
|
response = cmdSet.sign(smallData, WalletApplet.SIGN_DATA,false, true);
|
||||||
|
@ -382,6 +393,52 @@ public class WalletAppletTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Sign actual Ethereum transaction")
|
||||||
|
@Tag("manual")
|
||||||
|
void signTransactionTest() throws Exception {
|
||||||
|
// Initialize credentials
|
||||||
|
Web3j web3j = Web3j.build(new HttpService());
|
||||||
|
Credentials wallet1 = WalletUtils.loadCredentials("testwallet", "testwallets/wallet1.json");
|
||||||
|
Credentials wallet2 = WalletUtils.loadCredentials("testwallet", "testwallets/wallet2.json");
|
||||||
|
|
||||||
|
// Load keys on card
|
||||||
|
cmdSet.openSecureChannel();
|
||||||
|
ResponseAPDU response = cmdSet.verifyPIN("000000");
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
response = cmdSet.loadKey(wallet1.getEcKeyPair());
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
|
||||||
|
// Verify balance
|
||||||
|
System.out.println("Wallet 1 balance: " + web3j.ethGetBalance(wallet1.getAddress(), DefaultBlockParameterName.LATEST).send().getBalance());
|
||||||
|
System.out.println("Wallet 2 balance: " + web3j.ethGetBalance(wallet2.getAddress(), DefaultBlockParameterName.LATEST).send().getBalance());
|
||||||
|
|
||||||
|
BigInteger gasPrice = web3j.ethGasPrice().send().getGasPrice();
|
||||||
|
BigInteger weiValue = Convert.toWei(BigDecimal.valueOf(1.0), Convert.Unit.FINNEY).toBigIntegerExact();
|
||||||
|
BigInteger nonce = web3j.ethGetTransactionCount(wallet1.getAddress(), DefaultBlockParameterName.LATEST).send().getTransactionCount();
|
||||||
|
|
||||||
|
RawTransaction rawTransaction = RawTransaction.createEtherTransaction(nonce, gasPrice, Transfer.GAS_LIMIT, wallet2.getAddress(), weiValue);
|
||||||
|
|
||||||
|
byte[] txBytes = TransactionEncoder.encode(rawTransaction);
|
||||||
|
byte[] hash = Hash.sha3(txBytes);
|
||||||
|
|
||||||
|
response = cmdSet.sign(hash, WalletApplet.SIGN_PRECOMPUTED_HASH,true, true);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
byte[] sig = secureChannel.decryptAPDU(response.getData());
|
||||||
|
|
||||||
|
Method encode = TransactionEncoder.class.getDeclaredMethod("encode", RawTransaction.class, Sign.SignatureData.class);
|
||||||
|
encode.setAccessible(true);
|
||||||
|
byte[] signedMessage = (byte[]) encode.invoke(null, rawTransaction, new Sign.SignatureData((byte) 0, null, null)); //TODO: generate signature data
|
||||||
|
String hexValue = "0x" + Hex.toHexString(signedMessage);
|
||||||
|
EthSendTransaction ethSendTransaction = web3j.ethSendRawTransaction(hexValue).send();
|
||||||
|
|
||||||
|
if (ethSendTransaction.hasError()) {
|
||||||
|
System.out.println("Transaction Error: " + ethSendTransaction.getError().getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertFalse(ethSendTransaction.hasError());
|
||||||
|
}
|
||||||
|
|
||||||
private KeyPairGenerator keypairGenerator() throws Exception {
|
private KeyPairGenerator keypairGenerator() throws Exception {
|
||||||
ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1");
|
ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1");
|
||||||
KeyPairGenerator g = KeyPairGenerator.getInstance("ECDH", "BC");
|
KeyPairGenerator g = KeyPairGenerator.getInstance("ECDH", "BC");
|
||||||
|
@ -389,4 +446,18 @@ public class WalletAppletTest {
|
||||||
|
|
||||||
return g;
|
return g;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void reset() {
|
||||||
|
if (USE_SIMULATOR) {
|
||||||
|
simulator.reset();
|
||||||
|
} else {
|
||||||
|
apduChannel.getCard().getATR();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetAndSelectAndOpenSC() throws CardException {
|
||||||
|
reset();
|
||||||
|
cmdSet.select();
|
||||||
|
cmdSet.openSecureChannel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue