From fb24995cee4893542189bd371c15c3792fdcf944 Mon Sep 17 00:00:00 2001 From: Michele Balistreri Date: Thu, 5 Oct 2017 14:10:49 +0300 Subject: [PATCH] add web3j based test (incomplete) --- build.gradle | 9 ++ .../status/wallet/WalletAppletCommandSet.java | 35 +++++++ .../im/status/wallet/WalletAppletTest.java | 95 ++++++++++++++++--- 3 files changed, 127 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index 77cac16..ca6b282 100644 --- a/build.gradle +++ b/build.gradle @@ -33,11 +33,20 @@ repositories { dependencies { 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.junit.jupiter:junit-jupiter-api:5.0.0") testRuntime("org.junit.jupiter:junit-jupiter-engine:5.0.0") } +junitPlatform { + filters { + tags { + exclude 'manual' + } + } +} + sourceCompatibility = 1.3 targetCompatibility = 1.3 diff --git a/src/test/java/im/status/wallet/WalletAppletCommandSet.java b/src/test/java/im/status/wallet/WalletAppletCommandSet.java index df14009..1ab5c9b 100644 --- a/src/test/java/im/status/wallet/WalletAppletCommandSet.java +++ b/src/test/java/im/status/wallet/WalletAppletCommandSet.java @@ -4,6 +4,7 @@ import javacard.framework.ISO7816; import org.bouncycastle.jce.interfaces.ECPrivateKey; import org.bouncycastle.jce.interfaces.ECPublicKey; import org.bouncycastle.util.encoders.Hex; +import org.web3j.crypto.ECKeyPair; import javax.smartcardio.CardChannel; import javax.smartcardio.CardException; @@ -75,6 +76,40 @@ public class WalletAppletCommandSet { 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 { CommandAPDU loadKey = new CommandAPDU(0x80, WalletApplet.INS_LOAD_KEY, keyType, 0, secureChannel.encryptAPDU(data)); return apduChannel.transmit(loadKey); diff --git a/src/test/java/im/status/wallet/WalletAppletTest.java b/src/test/java/im/status/wallet/WalletAppletTest.java index cec8aff..0a4c799 100644 --- a/src/test/java/im/status/wallet/WalletAppletTest.java +++ b/src/test/java/im/status/wallet/WalletAppletTest.java @@ -8,31 +8,48 @@ import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.util.encoders.Hex; 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 java.lang.reflect.Method; +import java.math.BigDecimal; +import java.math.BigInteger; import java.security.*; import java.util.Arrays; import java.util.Random; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @DisplayName("Test the Wallet Applet") public class WalletAppletTest { private static CardTerminal cardTerminal; private static CardChannel apduChannel; + private static CardSimulator simulator; private SecureChannelSession secureChannel; 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 static void initAll() throws CardException { Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); if (USE_SIMULATOR) { - CardSimulator simulator = new CardSimulator(); + simulator = new CardSimulator(); AID appletAID = AIDUtil.create(WalletAppletCommandSet.APPLET_AID); byte[] instParams = Hex.decode("0F53746174757357616C6C657441707001000C313233343536373839303132"); simulator.installApplet(appletAID, WalletApplet.class, instParams, (short) 0, (byte) instParams.length); @@ -54,7 +71,7 @@ public class WalletAppletTest { @BeforeEach void init() throws CardException { - apduChannel.getCard().getATR(); + reset(); cmdSet = new WalletAppletCommandSet(apduChannel); byte[] keyData = cmdSet.select().getData(); secureChannel = new SecureChannelSession(keyData); @@ -146,9 +163,7 @@ public class WalletAppletTest { assertEquals(0x9000, response.getSW()); // Reset card and verify that the new PIN has really been set - apduChannel.getCard().getATR(); - cmdSet.select(); - cmdSet.openSecureChannel(); + resetAndSelectAndOpenSC(); response = cmdSet.verifyPIN("654321"); assertEquals(0x9000, response.getSW()); @@ -207,9 +222,7 @@ public class WalletAppletTest { assertEquals(0x9000, response.getSW()); // Check that PIN has been changed and unblocked - apduChannel.getCard().getATR(); - cmdSet.select(); - cmdSet.openSecureChannel(); + resetAndSelectAndOpenSC(); response = cmdSet.verifyPIN("654321"); assertEquals(0x9000, response.getSW()); @@ -353,9 +366,7 @@ public class WalletAppletTest { // Signing session is aborted on reselection response = cmdSet.sign(data, WalletApplet.SIGN_DATA,true, false); assertEquals(0x9000, response.getSW()); - apduChannel.getCard().getATR(); - cmdSet.select(); - cmdSet.openSecureChannel(); + resetAndSelectAndOpenSC(); response = cmdSet.verifyPIN("000000"); assertEquals(0x9000, response.getSW()); 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 { ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1"); KeyPairGenerator g = KeyPairGenerator.getInstance("ECDH", "BC"); @@ -389,4 +446,18 @@ public class WalletAppletTest { return g; } + + private void reset() { + if (USE_SIMULATOR) { + simulator.reset(); + } else { + apduChannel.getCard().getATR(); + } + } + + private void resetAndSelectAndOpenSC() throws CardException { + reset(); + cmdSet.select(); + cmdSet.openSecureChannel(); + } }