Java tutorial
/* * java API for the SatoChip Bitcoin Hardware Wallet * (c) 2015 by Toporin - 16DMCk4WUaHofchAhpMaQS4UPm4urcy2dN * Sources available on https://github.com/Toporin * * Copyright 2015 by Toporin (https://github.com/Toporin) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.satochip.satochipclient; import com.google.bitcoin.core.ECKey; import static com.google.bitcoin.core.Message.UNKNOWN_LENGTH; import com.google.bitcoin.core.NetworkParameters; import com.google.bitcoin.core.Sha256Hash; import com.google.bitcoin.core.Transaction; import static com.google.bitcoin.core.Transaction.SIGHASH_ANYONECANPAY_VALUE; import com.google.bitcoin.core.Transaction.SigHash; import com.google.bitcoin.core.TransactionInput; import com.google.bitcoin.core.TransactionOutput; import com.google.bitcoin.core.UnsafeByteArrayOutputStream; import com.google.bitcoin.core.Utils; import static com.google.bitcoin.core.Utils.uint32ToByteStreamLE; import com.google.bitcoin.crypto.DeterministicKey; import com.google.bitcoin.crypto.HDKeyDerivation; import com.google.bitcoin.crypto.TransactionSignature; import com.google.bitcoin.params.RegTestParams; import com.google.bitcoin.script.Script; import com.google.bitcoin.script.ScriptOpCodes; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import static java.util.concurrent.TimeUnit.MILLISECONDS; import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.Logger; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.smartcardio.CardException; import javax.xml.bind.DatatypeConverter; import org.bouncycastle.crypto.digests.SHA256Digest; import org.junit.After; import org.junit.AfterClass; import static org.junit.Assert.*; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import static org.satochip.satochipclient.CardDataParser.toHexString; import org.toporin.bitcoincore.ECException; import org.toporin.yubikey4java.YubikeyConnector; public class CardConnectorTest { public static final byte[] BYTE_AID = { 0x53, 0x61, 0x74, 0x6f, 0x43, 0x68, 0x69, 0x70 }; //SatoChip // setup params done only once public static byte pin_tries_0 = 0x10; public static byte ublk_tries_0 = 0x10; public static byte[] pin_0 = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38 }; public static byte[] ublk_0 = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38 }; public static byte pin_tries_1 = 0x10; public static byte ublk_tries_1 = 0x10; public static byte[] pin_1 = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38 }; public static byte[] ublk_1 = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38 }; public static short secmemsize = 0x1000; public static short memsize = 0x1000; public static byte create_object_ACL = 0x01; public static byte create_key_ACL = 0x01; public static byte create_pin_ACL = 0x01; public static short option_flags = (short) 0x8000; // activate 2fa with hmac challenge-response public static byte[] key = new byte[20]; public static long amount_limit = 0; // static test public static CardConnector cc; public static byte std_keynbr = 0x00; public static byte bip32_keynbr = (byte) 0xff; // test PIN public static byte pin2_nbr = 2; public static byte pin2_tries = 3; public static byte[] pin2 = { 30, 30, 30, 30 }; public static byte[] ublk2 = { 31, 31, 31, 31 }; // test object public static final byte[] DEFAULT_ACL = { 0x00, 0x01, 0x00, 0x01, 0x00, 0x01 }; // test BIP32 public static String strseed = "31323334353637383132333435363738";// ascii for 1234567812345678 public static byte[] authentikey = null; public static DeterministicKey masterkey; // test message signing public String strmsg = "abcdefghijklmnopqrstuvwxyz0123456789"; public String strmsg_long = ""; public byte[] default_bip32path = { (byte) 0x80, 0x00, 0x00, 0x00 }; public CardConnectorTest() { } @BeforeClass public static void setUpClass() throws ECException, Exception { System.out.println("* CardConnectorTest: @BeforeClass method"); ConsoleHandler handler = new ConsoleHandler(); handler.setLevel(Level.INFO); cc = new CardConnector(handler, Level.INFO); try { System.out.println("cardSelect:"); cc.cardSelect(BYTE_AID); try { System.out.println("cardSetup:"); cc.cardSetup(pin_tries_0, ublk_tries_0, pin_0, ublk_0, pin_tries_1, ublk_tries_1, pin_1, ublk_1, secmemsize, memsize, create_object_ACL, create_key_ACL, create_pin_ACL //,option_flags, key, amount_limit ); } catch (CardConnectorException ex) { if (ex.getSW12() != 0x6d00) fail("Unable to set up applet"); else System.out.println("setup already done"); } // required for other tests cc.cardVerifyPIN((byte) 0, pin_0); testCardBip32ImportSeed(); testCardGenerateKeyPair(JCconstants.ALG_EC_FP, std_keynbr, (byte) 0xff, (short) 256); } catch (CardConnectorException ex) { System.out.println("CardConnectorException: " + ex.getMessage() + " " + Integer.toHexString(ex.getIns() & 0xff) + " " + Integer.toHexString(ex.getSW12() & 0xffff)); } } @AfterClass public static void tearDownClass() { System.out.println("* CardConnectorTest: @AfterClass method"); /* Mise hors tension de la carte */ System.out.println("Disconnect..."); try { cc.disconnect(); } catch (CardException ex) { //Logger.getLogger(CardConnectorTest.class.getName()).log(Level.SEVERE, null, ex); fail("Unable to disconnect card"); } } @Before public void setUp() { // try { // System.out.println("* CardConnectorTest: @Before method"); // cc.cardGetStatus(); // } catch (CardConnectorException ex) { // Logger.getLogger(CardConnectorTest.class.getName()).log(Level.SEVERE, null, ex); // fail("Unable to get card status"); // } } @After public void tearDown() { // try { // System.out.println("* CardConnectorTest: @After method"); // cc.cardGetStatus(); // // } catch (CardConnectorException ex) { // Logger.getLogger(CardConnectorTest.class.getName()).log(Level.SEVERE, null, ex); // fail("Unable to get card status"); // } } /** * Test of cardBip32ImportSeed method, of class CardConnector. */ public static void testCardBip32ImportSeed() throws Exception { System.out.println("cardBip32ImportSeed"); // import seed to HWchip long startTime = System.currentTimeMillis(); byte[] seed = DatatypeConverter.parseHexBinary(strseed); byte[] seed_ACL = DEFAULT_ACL; //{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; byte[] response = cc.cardBip32ImportSeed(seed_ACL, seed); long stopTime = System.currentTimeMillis(); long elapsedTime = stopTime - startTime; System.out.println("elapsed time: " + elapsedTime); org.satochip.satochipclient.CardDataParser.PubKeyData parser = new CardDataParser.PubKeyData(); authentikey = parser.parseBip32ImportSeed(response).authentikey; System.out.println("authentikey: " + CardDataParser.toHexString(authentikey)); // create SW masterkey equivalent with bitcoinj masterkey = HDKeyDerivation.createMasterPrivateKey(seed); } /** * Test of cardBip32GetAuthentiKey method, of class CardConnector. */ @Test public void testCardBip32GetAuthentiKey() throws Exception { System.out.println("cardBip32GetAuthentiKey"); byte[] response = cc.cardBip32GetAuthentiKey(); org.satochip.satochipclient.CardDataParser.PubKeyData pubkeydata = new CardDataParser.PubKeyData(); byte[] recoveredkey = pubkeydata.parseBip32GetAuthentikey(response).authentikey; System.out.println("recoveredkey: " + CardDataParser.toHexString(recoveredkey)); assertArrayEquals(recoveredkey, authentikey); } /** * Test of cardBip32GetExtendedKey method, of class CardConnector. */ @Test public void testCardBip32GetExtendedKey() throws Exception { System.out.println("cardBip32GetExtendedKey"); int valmax = 0; int depthmax = 3; byte[] keyhw, keysw; for (int val = 0; val <= valmax; val++) { for (int depth = 1; depth <= depthmax; depth++) { byte[] bip32path = new byte[4 * depth]; // normal child for (int i = 0; i < bip32path.length; i += 4) { bip32path[i] = 0x00; bip32path[i + 1] = 0x00; bip32path[i + 2] = 0x00; bip32path[i + 3] = (byte) val; } keyhw = testCardBip32GetExtendedKey(bip32path);//HW keysw = testCardBip32GetExtendedKey_bitcoinj(bip32path);//SW assertArrayEquals(keyhw, keysw); // hardened child for (int i = 0; i < bip32path.length; i += 4) { bip32path[i] = (byte) 0x80; bip32path[i + 1] = 0x00; bip32path[i + 2] = 0x00; bip32path[i + 3] = (byte) val; } keyhw = testCardBip32GetExtendedKey(bip32path);//HW keysw = testCardBip32GetExtendedKey_bitcoinj(bip32path);//SW assertArrayEquals(keyhw, keysw); } } } public byte[] testCardBip32GetExtendedKey(byte[] bip32path) throws Exception { long startTime = System.currentTimeMillis(); byte[] response = cc.cardBip32GetExtendedKey(bip32path); long stopTime = System.currentTimeMillis(); long elapsedTime = stopTime - startTime; System.out.println("elapsed time: " + elapsedTime); System.out.println("Extended key for " + toHexString(bip32path)); org.satochip.satochipclient.CardDataParser.PubKeyData pubkeydata = new CardDataParser.PubKeyData( authentikey); byte[] recoveredkey = pubkeydata.parseBip32GetExtendedKey(response).pubkey; System.out.println("extendedkey: " + CardDataParser.toHexString(recoveredkey)); return recoveredkey; } public byte[] testCardBip32GetExtendedKey_bitcoinj(byte[] bip32path) throws Exception { // create SW extendedkey with bitcoinj int bip32depth = bip32path.length / 4; DeterministicKey parent = masterkey; // imported from seed DeterministicKey child = null; for (int i = 0; i < bip32path.length; i += 4) { int childNumber = ((bip32path[i] & 0xff) << 24) ^ ((bip32path[i + 1] & 0xff) << 16) ^ ((bip32path[i + 2] & 0xff) << 8) ^ (bip32path[i + 3] & 0xff); child = HDKeyDerivation.deriveChildKey(parent, childNumber); parent = child; //System.out.println("Depth:"+(i/4+1)+" pubKey:" + parent.toString()); } System.out.println("Extended pubKey:" + parent.toString()); return parent.getPubKeyBytes(); } /** * Test of cardSignMessage method, of class CardConnector. */ @Test public void testCardSignMessage() throws Exception { System.out.println("cardSignMessage"); testCardSignMessage(strmsg, bip32_keynbr); testCardSignMessage(strmsg, std_keynbr); testCardSignMessage(strmsg_long, bip32_keynbr); testCardSignMessage(strmsg_long, std_keynbr); } public void testCardSignMessage(String strmsg, byte keynbr) throws Exception { // recover pubkey byte[] pubkey; if (keynbr == bip32_keynbr) pubkey = testCardBip32GetExtendedKey(default_bip32path); else pubkey = testGetPublicKeyFromPrivate(keynbr); System.out.println("signing pubkey: " + toHexString(pubkey)); // sign message byte[] msg = strmsg.getBytes(); byte[] signature; if (msg.length < 144) signature = cc.cardSignShortMessage(keynbr, msg); else signature = cc.cardSignMessage(keynbr, msg); // parse signature System.out.println("signature: " + toHexString(signature)); org.satochip.satochipclient.CardDataParser.PubKeyData pubkeydata = new CardDataParser.PubKeyData(); String strsignature64 = pubkeydata.parseMessageSigning(signature, pubkey, strmsg).compactsig_b64_str; System.out.println("signature in base64: " + strsignature64); // verify with bitcoinj ECKey eckey = ECKey.signedMessageToKey(strmsg, strsignature64); System.out.println("recovered pubkey: " + toHexString(eckey.getPubKey())); assertArrayEquals(pubkey, eckey.getPubKey()); } // /** // * Test of cardParseTransaction method, of class CardConnector. // */ // @Test // public void testCardParseTransaction() throws Exception { // System.out.println("cardParseTransaction"); // // testCardParseTransaction(bip32_keynbr); // testCardParseTransaction(std_keynbr); // // // } /** * Test of cardParseTx method, of class CardConnector. */ @Test public void testCardParseTx() throws Exception { System.out.println("cardParseTx"); testCardParseTransaction(bip32_keynbr); testCardParseTransaction(std_keynbr); } public void testCardParseTransaction(byte keynbr) throws CardConnectorException, ECException { // recover pubkey byte[] pubkey, response; CardDataParser.PubKeyData dataparser = new CardDataParser.PubKeyData(authentikey); if (keynbr == bip32_keynbr) { response = cc.cardBip32GetExtendedKey(default_bip32path); authentikey = dataparser.parseBip32GetExtendedKey(response).authentikey; pubkey = dataparser.pubkey; } else { response = cc.cardGetPublicKeyFromPrivate(keynbr); pubkey = dataparser.parseGetPublicKeyFromPrivate(response).pubkey; } // bitcoinj NetworkParameters params; params = RegTestParams.get(); Transaction tx = new Transaction(params); ECKey serverKey = new ECKey(null, pubkey, true); BigInteger nanoCoins = Utils.toNanoCoins(1, 0); TransactionOutput outputToMe = new TransactionOutput(params, tx, nanoCoins, serverKey); // simple tx tx.addOutput(outputToMe); tx.addInput(new TransactionInput(params, tx, outputToMe.getScriptBytes())); int inputIndex = 0; byte[] connectedScript = outputToMe.getScriptBytes(); byte sigHashType = (byte) TransactionSignature.calcSigHashValue(SigHash.ALL, false); byte[] rawtxforhashing = byteArrayForSignature(tx, inputIndex, connectedScript, sigHashType); // unused System.out.println("Raw tx for hashing:" + toHexString(rawtxforhashing)); byte[] rawtxhash = new byte[32]; SHA256Digest sha256 = new SHA256Digest(); sha256.reset(); sha256.update(rawtxforhashing, 0, rawtxforhashing.length); sha256.doFinal(rawtxhash, 0); //System.out.println("Raw tx singlehash:" + toString(rawtxhash)); sha256.reset(); sha256.update(rawtxhash, 0, rawtxhash.length); sha256.doFinal(rawtxhash, 0); //System.out.println("Raw tx doublehash:" + toString(rawtxhash)); Sha256Hash rawtxhash2 = tx.hashForSignature(inputIndex, connectedScript, sigHashType); byte[] txhash_sw = rawtxhash2.getBytes(); System.out.println("Tx hash Bitcoinj: " + toHexString(txhash_sw)); // send to card for parsing //byte[] response= cc.cardParseTransaction(rawtxforhashing); response = cc.cardParseTx(rawtxforhashing); CardDataParser.PubKeyData txparser = new CardDataParser.PubKeyData(authentikey); byte[] txhash_hw = txparser.parseTxHash(response).data; //Arrays.copyOfRange(response, 2, 2+32); System.out.println("Tx hash SatoChip: " + toHexString(txhash_hw)); System.out.println(txparser.toString()); assertArrayEquals(txhash_hw, txhash_sw); // check if 2fa is required boolean need_2fa_chalresp = ((txparser.option_flags & 0x8000) == 0x8000) ? true : false; // if msb is set, a challenge-response 2nd factor authentification is needed byte[] txhmac = null; if (need_2fa_chalresp) { try { System.out.println("Second factor authentication required for challenge response..."); System.out.println("Please insert a configured yubikey!"); MILLISECONDS.sleep(2000); } catch (InterruptedException ex) { } YubikeyConnector yubikey = new YubikeyConnector(false); yubikey.findYubikey(YubikeyConnector.PRODUCT_ID_NEO); yubikey.openYubikey(); yubikey.attachYubikeyInterface(); txhmac = yubikey.challenge_response(txhash_hw, YubikeyConnector.MODE_HMAC, YubikeyConnector.SLOT_2, false, true); yubikey.releaseYubikeyInterface(); yubikey.closeYubikey(); System.out.println("txhmac: " + toHexString(txhmac)); // test with wrong hmac: //txhmac[0]=0; } byte[] txsign = cc.cardSignTransaction(keynbr, txhash_hw, txhmac); System.out.println("txsign: " + toHexString(txsign)); } // from Bitcoinj - com.google.bitcoin.core.Transaction // return a byte array of the transaction serialized data to be hashed for signing public static byte[] byteArrayForSignature(Transaction tx, int inputIndex, byte[] connectedScript, byte sigHashType) { // The SIGHASH flags are used in the design of contracts, please see this page for a further understanding of // the purposes of the code in this method: // // https://en.bitcoin.it/wiki/Contracts byte[] EMPTY_ARRAY = new byte[0]; NetworkParameters params; params = RegTestParams.get(); try { // This step has no purpose beyond being synchronized with the reference clients bugs. OP_CODESEPARATOR // is a legacy holdover from a previous, broken design of executing scripts that shipped in Bitcoin 0.1. // It was seriously flawed and would have let anyone take anyone elses money. Later versions switched to // the design we use today where scripts are executed independently but share a stack. This left the // OP_CODESEPARATOR instruction having no purpose as it was only meant to be used internally, not actually // ever put into scripts. Deleting OP_CODESEPARATOR is a step that should never be required but if we don't // do it, we could split off the main chain. connectedScript = Script.removeAllInstancesOfOp(connectedScript, ScriptOpCodes.OP_CODESEPARATOR); // Store all the input scripts and clear them in preparation for signing. If we're signing a fresh // transaction that step isn't very helpful, but it doesn't add much cost relative to the actual // EC math so we'll do it anyway. // Also store the input sequence numbers in case we are clearing them with SigHash.NONE/SINGLE byte[][] inputScripts = new byte[tx.getInputs().size()][]; long[] inputSequenceNumbers = new long[tx.getInputs().size()]; Transaction txcpy = new Transaction(params); for (int i = 0; i < tx.getInputs().size(); i++) { inputScripts[i] = tx.getInputs().get(i).getScriptBytes(); inputSequenceNumbers[i] = tx.getInputs().get(i).getSequenceNumber(); //inputs.get(i).setScriptBytes(EMPTY_ARRAY); if (i == inputIndex) txcpy.addInput(new TransactionInput(params, txcpy, connectedScript)); else txcpy.addInput(new TransactionInput(params, txcpy, EMPTY_ARRAY)); txcpy.getInputs().get(i).setSequenceNumber(tx.getInputs().get(i).getSequenceNumber()); } for (int o = 0; o < tx.getOutputs().size(); o++) { txcpy.addOutput(tx.getOutput(o).getValue(), new Script(tx.getOutput(o).getScriptBytes())); } // Set the input to the script of its output. Satoshi does this but the step has no obvious purpose as // the signature covers the hash of the prevout transaction which obviously includes the output script // already. Perhaps it felt safer to him in some way, or is another leftover from how the code was written. //TransactionInput input = tx.getInputs().get(inputIndex); //input.setScriptBytes(connectedScript); //ArrayList<TransactionOutput> outputs = (ArrayList<TransactionOutput>) tx.getOutputs(); if ((sigHashType & 0x1f) == (SigHash.NONE.ordinal() + 1)) { // // SIGHASH_NONE means no outputs are signed at all - the signature is effectively for a "blank cheque". // this.outputs = new ArrayList<TransactionOutput>(0); // // The signature isn't broken by new versions of the transaction issued by other parties. // for (int i = 0; i < tx.getInputs().size(); i++) // if (i != inputIndex) // txcpy.getInputs().get(i).setSequenceNumber(0); return null; // not supported } else if ((sigHashType & 0x1f) == (SigHash.SINGLE.ordinal() + 1)) { // // SIGHASH_SINGLE means only sign the output at the same index as the input (ie, my output). // if (inputIndex >= this.outputs.size()) { // // The input index is beyond the number of outputs, it's a buggy signature made by a broken // // Bitcoin implementation. The reference client also contains a bug in handling this case: // // any transaction output that is signed in this case will result in both the signed output // // and any future outputs to this public key being steal-able by anyone who has // // the resulting signature and the public key (both of which are part of the signed tx input). // // Put the transaction back to how we found it. // // // // TODO: Only allow this to happen if we are checking a signature, not signing a transactions // for (int i = 0; i < inputs.size(); i++) { // inputs.get(i).setScriptBytes(inputScripts[i]); // inputs.get(i).setSequenceNumber(inputSequenceNumbers[i]); // } // this.outputs = outputs; // // Satoshis bug is that SignatureHash was supposed to return a hash and on this codepath it // // actually returns the constant "1" to indicate an error, which is never checked for. Oops. // return new String("0100000000000000000000000000000000000000000000000000000000000000").getBytes(); // added // //return new Sha256Hash("0100000000000000000000000000000000000000000000000000000000000000"); // } // // In SIGHASH_SINGLE the outputs after the matching input index are deleted, and the outputs before // // that position are "nulled out". Unintuitively, the value in a "null" transaction is set to -1. // this.outputs = new ArrayList<TransactionOutput>(this.outputs.subList(0, inputIndex + 1)); // for (int i = 0; i < inputIndex; i++) // this.outputs.set(i, new TransactionOutput(params, this, NEGATIVE_ONE, new byte[] {})); // // The signature isn't broken by new versions of the transaction issued by other parties. // for (int i = 0; i < inputs.size(); i++) // if (i != inputIndex) // inputs.get(i).setSequenceNumber(0); return null; // not supported } //ArrayList<TransactionInput> inputs = (ArrayList<TransactionInput>) txcpy.getInputs(); if ((sigHashType & SIGHASH_ANYONECANPAY_VALUE) == SIGHASH_ANYONECANPAY_VALUE) { // // SIGHASH_ANYONECANPAY means the signature in the input is not broken by changes/additions/removals // // of other inputs. For example, this is useful for building assurance contracts. // this.inputs = new ArrayList<TransactionInput>(); // this.inputs.add(input); return null; // not supported } ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream( txcpy.getMessageSize() == UNKNOWN_LENGTH ? 256 : txcpy.getMessageSize() + 4); txcpy.bitcoinSerialize(bos); // We also have to write a hash type (sigHashType is actually an unsigned char) uint32ToByteStreamLE(0x000000ff & sigHashType, bos); // Note that this is NOT reversed to ensure it will be signed correctly. If it were to be printed out // however then we would expect that it is IS reversed. //Sha256Hash hash = new Sha256Hash(singleDigest(bos.toByteArray(),0, bos.toByteArray().length)); // change: single digest! byte[] txdata = bos.toByteArray(); // added bos.close(); // Put the transaction back to how we found it. // this.inputs = inputs; // for (int i = 0; i < inputs.size(); i++) { // inputs.get(i).setScriptBytes(inputScripts[i]); // inputs.get(i).setSequenceNumber(inputSequenceNumbers[i]); // } // this.outputs = outputs; return txdata; // return hash; } catch (IOException e) { throw new RuntimeException(e); // Cannot happen. } } /** * Test of cardGenerateSymmetricKey method, of class CardConnector. */ @Test public void testCardGenerateSymmetricKey() throws Exception { System.out.println("cardGenerateSymmetricKey"); testCardGenerateSymmetricKey(JCconstants.TYPE_DES, (byte) 0x0A, (short) 64); testCardGenerateSymmetricKey(JCconstants.TYPE_DES, (byte) 0x0B, (short) 128); testCardGenerateSymmetricKey(JCconstants.TYPE_DES, (byte) 0x0C, (short) 192); testCardGenerateSymmetricKey(JCconstants.TYPE_AES, (byte) 0x0D, (short) 128); testCardGenerateSymmetricKey(JCconstants.TYPE_AES, (byte) 0x0E, (short) 192); testCardGenerateSymmetricKey(JCconstants.TYPE_AES, (byte) 0x0F, (short) 256); System.out.println("cardComputeCrypt"); //TestComputeCrypt(cc, JCconstants.ALG_RSA_PKCS1, (byte)0x01, (byte)0x00); // //TestComputeCrypt(cc, JCconstants.ALG_RSA_NOPAD, (byte)0x01, (byte)0x00); // to do: padding testComputeCrypt(JCconstants.ALG_DES_CBC_NOPAD, (byte) 0x0A, (byte) 0x0A); testComputeCrypt(JCconstants.ALG_DES_ECB_NOPAD, (byte) 0x0B, (byte) 0x0B); testComputeCrypt(JCconstants.ALG_DES_CBC_NOPAD, (byte) 0x0C, (byte) 0x0C); testComputeCrypt(JCconstants.ALG_AES_BLOCK_128_CBC_NOPAD, (byte) 0x0D, (byte) 0x0D); testComputeCrypt(JCconstants.ALG_AES_BLOCK_128_ECB_NOPAD, (byte) 0x0E, (byte) 0x0E); testComputeCrypt(JCconstants.ALG_AES_BLOCK_128_CBC_NOPAD, (byte) 0x0F, (byte) 0x0F); // list key byte[] response; response = cc.cardGetStatus(); CardDataParser.CardStatus cardstatus = new CardDataParser.CardStatus(response); System.out.println(cardstatus.toString()); response = cc.cardListKeys(); CardDataParser.KeyList keylist = new CardDataParser.KeyList(response); System.out.println(keylist.toString()); } public void testCardGenerateSymmetricKey(byte algtype, byte keynbr, short keysize) throws Exception { byte[] keyACL = DEFAULT_ACL; String stralg = ""; if (algtype == JCconstants.TYPE_DES) stralg = "DES"; else if (algtype == JCconstants.TYPE_AES) stralg = "AES"; else { System.out.println("ERROR: algorithm not supported!"); return; } System.out.println( "Test GenerateKey(alg=" + stralg + ", keynbr=" + (int) keynbr + ", keysize=" + keysize + ")"); cc.cardGenerateSymmetricKey(keynbr, algtype, keysize, keyACL); } public static void testComputeCrypt(byte CM, byte keynbr, byte keynbrdecrypt) throws Exception { String stralg = ""; if (CM == JCconstants.ALG_RSA_PKCS1) stralg = "RSApkcs1"; else if (CM == JCconstants.ALG_RSA_NOPAD) stralg = "RSAnopad"; else if (CM == JCconstants.ALG_AES_BLOCK_128_CBC_NOPAD) stralg = "AES-128-CBC"; else if (CM == JCconstants.ALG_AES_BLOCK_128_ECB_NOPAD) stralg = "AES-128-ECB"; else if (CM == JCconstants.ALG_DES_CBC_NOPAD) stralg = "DES-128-CBC"; else if (CM == JCconstants.ALG_DES_ECB_NOPAD) stralg = "DES-128-ECB"; else { System.out.println("ERROR: mode not supported!"); return; } String strmsg = "abcdef"; byte[] msg; byte[] msgcrypt; byte[] msgdecrypt; for (int i = 0; i < 6; i++) { msg = strmsg.getBytes(); System.out .println("\t TestComputeCrypt(CM=" + stralg + ", keynbr=" + keynbr + "-" + keynbrdecrypt + ")"); msgcrypt = cc.cardComputeCrypt(keynbr, CM, JCconstants.MODE_ENCRYPT, msg); msgdecrypt = cc.cardComputeCrypt(keynbrdecrypt, CM, JCconstants.MODE_DECRYPT, msgcrypt); //System.out.println("msg:"+toString(msg)); //System.out.println("msgcrypt:"+toString(msgcrypt)); //System.out.println("msgdecrypt:"+toString(msgdecrypt)); assertArrayEquals(msg, msgdecrypt); strmsg += strmsg; } } /** * Test of cardImportKey method, of class CardConnector. */ @Test public void testCardImportKey() throws Exception { System.out.println("cardImportKey"); //testImportKey(JCconstants.TYPE_RSA_CRT_PRIVATE, (byte)0x00, (short)512); testImportKey(JCconstants.TYPE_RSA_PUBLIC, (byte) 0x01, (short) 512); testImportKey(JCconstants.TYPE_RSA_PRIVATE, (byte) 0x02, (short) 512); testImportKey(JCconstants.TYPE_RSA_PUBLIC, (byte) 0x03, (short) 512); testImportKey(JCconstants.TYPE_EC_FP_PRIVATE, (byte) 0x06, (short) 256); testGetPublicKeyFromPrivate((byte) 0x06); //TestImportKey(cc, JCconstants.TYPE_EC_FP_PUBLIC, (byte)0x07, (short)256); // doesn't work? testImportKey(JCconstants.TYPE_DES, (byte) 0x0A, (short) 64); testImportKey(JCconstants.TYPE_DES, (byte) 0x0B, (short) 128); testImportKey(JCconstants.TYPE_DES, (byte) 0x0C, (short) 192); testImportKey(JCconstants.TYPE_AES, (byte) 0x0D, (short) 128); testImportKey(JCconstants.TYPE_AES, (byte) 0x0E, (short) 192); testImportKey(JCconstants.TYPE_AES, (byte) 0x0F, (short) 256); System.out.println("cardComputeCrypt"); //TestComputeCrypt(cc, JCconstants.ALG_RSA_PKCS1, (byte)0x01, (byte)0x02); //doesn't work? //TestComputeCrypt(cc, JCconstants.ALG_RSA_PKCS1, (byte)0x01, (byte)0x00); //doesn't work? //TestComputeCrypt(cc, JCconstants.ALG_RSA_NOPAD, (byte)0x01, (byte)0x00); // to do: padding testComputeCrypt(JCconstants.ALG_DES_CBC_NOPAD, (byte) 0x0A, (byte) 0x0A); testComputeCrypt(JCconstants.ALG_DES_ECB_NOPAD, (byte) 0x0B, (byte) 0x0B); testComputeCrypt(JCconstants.ALG_DES_CBC_NOPAD, (byte) 0x0C, (byte) 0x0C); testComputeCrypt(JCconstants.ALG_AES_BLOCK_128_CBC_NOPAD, (byte) 0x0D, (byte) 0x0D); testComputeCrypt(JCconstants.ALG_AES_BLOCK_128_ECB_NOPAD, (byte) 0x0E, (byte) 0x0E); testComputeCrypt(JCconstants.ALG_AES_BLOCK_128_CBC_NOPAD, (byte) 0x0F, (byte) 0x0F); // list key byte[] response; response = cc.cardGetStatus(); CardDataParser.CardStatus cardstatus = new CardDataParser.CardStatus(response); System.out.println(cardstatus.toString()); response = cc.cardListKeys(); CardDataParser.KeyList keylist = new CardDataParser.KeyList(response); System.out.println(keylist.toString()); } public static void testImportKey(byte key_type, byte key_nbr, short key_size) throws Exception { byte key_encoding = 0x00; //plain byte[] key_ACL = DEFAULT_ACL; //{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; String stralg = ""; String strkey = ""; short keysize = key_size; if (key_type == JCconstants.TYPE_EC_FP_PRIVATE) { stralg = "ECpriv"; keysize = 256; strkey = "0020" + "7bb8bfeb2ebc1401f9a14585032df07126ddf634ca641b7fa223b44b1e861548";//pycoin ku P:toporin } else if (key_type == JCconstants.TYPE_EC_FP_PUBLIC) { stralg = "ECpub"; keysize = 256; strkey = "0041" //short blob size (0x41=65) + "04" //uncompressed + "8d68936ac800d3fc1cf999bfe0a3af4ead4cf9ad61d3cb377c3e5626b5bfa9e8" // coordx + "d682abeb1337c9b97d114f757bdd81e0207ad673d736eb6b4a84890be5f92335";// coordy } else if (key_type == JCconstants.TYPE_RSA_PUBLIC) { stralg = "RSApub"; keysize = 512; strkey = "0040"// 0x40=64 modsize (byte) + "88d8b1c3ac39311ac82af63d6aeb3ea9cd05a28975cbc30203be81339f1341dac60e8afda1130e25e83e64e3112b9fb43c2e1ee47b8f6e164204c526bd7621e5" //mod + "0003" // expsize + "010001"; // exponent } else if (key_type == JCconstants.TYPE_RSA_PRIVATE) { stralg = "RSApriv"; keysize = 512; strkey = "0040"// 0x40=64 modsize (byte) + "88d8b1c3ac39311ac82af63d6aeb3ea9cd05a28975cbc30203be81339f1341dac60e8afda1130e25e83e64e3112b9fb43c2e1ee47b8f6e164204c526bd7621e5" //mod + "0040" // expsize + "60da7d762ffe8a729a194e0e4a0e155bb86fb489f585318fcb76999b1f8b519fa41e55ba3c6294b5eaf1dc333191299ea10f5ca8507c3f120111396686554641"; } else if (key_type == JCconstants.TYPE_RSA_CRT_PRIVATE) { stralg = "RSA-CRTpriv"; keysize = 512; strkey = "0020" + "f07c528f200b28b8e8ff4d73079730179bcec63b61a3012b849434ee4de389af"//P + "0020" + "91acbf0d2dc68b213b6dad87cddc580901f646401eee8c1946d395d44c45f6ab"//Q + "0020" + "264034c60f9b06db8721d655eacb8708ae68533f310b31cc879c16227857abdb"//Qinv + "0020" + "b6350bfc8343d133e0dd66da0bdb4245f0f846fbc0eb573c98b40e32ac7304e3"//DP1 + "0020" + "1907511bf68d7242176fd4accc95db1a5117fb21f12e932b949badd677f45d59";//DQ1 } else if (key_type == JCconstants.TYPE_AES && key_size == 128) { stralg = "AES-128"; keysize = 128; strkey = "0010" + "000102030405060708090a0b0c0d0e0f";//0x10=16 } else if (key_type == JCconstants.TYPE_AES && key_size == 192) { stralg = "AES-192"; keysize = 192; strkey = "0018" + "000102030405060708090a0b0c0d0e0f0001020304050607";//0x18=24 } else if (key_type == JCconstants.TYPE_AES && key_size == 256) { stralg = "AES-256"; keysize = 256; strkey = "0020" + "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f";//0x20=32 } else if (key_type == JCconstants.TYPE_DES && key_size == 64) { stralg = "DES-64"; keysize = 64; strkey = "0008" + "0001020304050607";//0x08=8 } else if (key_type == JCconstants.TYPE_DES && key_size == 128) { stralg = "DES-128"; keysize = 128; strkey = "0010" + "000102030405060708090a0b0c0d0e0f";//0x10=16 } else if (key_type == JCconstants.TYPE_DES && key_size == 192) { stralg = "DES-192"; keysize = 192; strkey = "0018" + "000102030405060708090a0b0c0d0e0f0001020304050607";//0x18=24 } else { System.out.println("ERROR: key type not supported!"); return; } byte[] keyblob = DatatypeConverter.parseHexBinary(strkey); System.out.println("TestImportKey(key=" + stralg + ", nb=" + (int) key_nbr + ", keysize=" + keysize + ")"); // jcop-ko); cc.cardImportKey(key_nbr, key_ACL, key_encoding, key_type, keysize, keyblob); // list key cc.cardGetStatus(); System.out.println("*****ListKey*****"); cc.cardListKeys(); System.out.println("*****************\n\n"); } /** * Test of cardGenerateKeyPair method, of class CardConnector. */ @Test public void testCardGenerateKeyPair() throws Exception { System.out.println("cardGenerateKeyPair"); //testCardGenerateKeyPair(JCconstants.ALG_RSA_CRT, (byte)0x00, (byte)0x01, (short)512);// doesn't work? testCardGenerateKeyPair(JCconstants.ALG_RSA, (byte) 0x02, (byte) 0x03, (short) 512); //testCardGenerateKeyPair(JCconstants.ALG_RSA, (byte)0x04, (byte)0x05, (short)1024); testCardGenerateKeyPair(JCconstants.ALG_EC_FP, (byte) 0x06, (byte) 0xff, (short) 256); System.out.println("cardGenerateKeyPair"); testGetPublicKeyFromPrivate((byte) 0x06); System.out.println("cardComputeSign"); // to do //TestComputeSign(byte CM, byte key_sign, byte key_verif); //testComputeSign(JCconstants.ALG_RSA_PKCS1, (byte) 2, (byte) 3); //TestComputeSign(JCconstants.ALG_ECDSA_SHA256, (byte) 4, (byte) 5); //TestComputeSign(JCconstants.CM_ECDSA_SHA, (byte) 4, (byte) 5); // list key byte[] response; response = cc.cardGetStatus(); CardDataParser.CardStatus cardstatus = new CardDataParser.CardStatus(response); System.out.println(cardstatus.toString()); response = cc.cardListKeys(); CardDataParser.KeyList keylist = new CardDataParser.KeyList(response); System.out.println(keylist.toString()); } public static void testCardGenerateKeyPair(byte alg_type, byte priv_key_nbr, byte pub_key_nbr, short key_size) throws CardConnectorException { byte[] priv_key_ACL = DEFAULT_ACL; // {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; byte[] pub_key_ACL = DEFAULT_ACL; //{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; byte gen_opt = JCconstants.OPT_DEFAULT; byte[] gen_opt_param = {}; String stralg = ""; if (alg_type == JCconstants.ALG_RSA) stralg = "RSA"; else if (alg_type == JCconstants.ALG_RSA_CRT) stralg = "RSA-CRT"; else if (alg_type == JCconstants.ALG_EC_FP) { stralg = "ECC"; gen_opt = JCconstants.OPT_EC_SECP256k1; } else { System.out.println("\t ERROR: algorithm not supported!"); return; } System.out.println("\t Test GenerateKey(alg=" + stralg + ", priv=" + (int) priv_key_nbr + ", pub=" + (int) pub_key_nbr + ", keysize=" + key_size + ")"); cc.cardGenerateKeyPair(priv_key_nbr, pub_key_nbr, alg_type, key_size, priv_key_ACL, pub_key_ACL, gen_opt, gen_opt_param); } public static byte[] testGetPublicKeyFromPrivate(byte keynbr) throws Exception { byte[] response = cc.cardGetPublicKeyFromPrivate(keynbr); CardDataParser.PubKeyData parser = new CardDataParser.PubKeyData(); byte[] pubkey = parser.parseGetPublicKeyFromPrivate(response).pubkey; return pubkey; } public static void testComputeSign(byte CM, byte key_sign, byte key_verif) throws CardConnectorException { byte[] buffer = new byte[512]; Arrays.fill(buffer, (byte) 30); byte[] buffer_wrong = { 'a', 'b', 'c', 'd', 'e' }; String stralg = ""; if (CM == JCconstants.ALG_RSA_PKCS1) stralg = "RSApkcs1"; else if (CM == JCconstants.ALG_RSA_NOPAD) stralg = "RSAnopad"; else if (CM == JCconstants.ALG_ECDSA_SHA) stralg = "ECDSAsha"; else if (CM == JCconstants.ALG_ECDSA_SHA_256) stralg = "ECDSAsha256"; else { System.out.println("ERROR: mode not supported!"); return; } // computesign System.out.println("\t Test ComputeSign(CM=" + stralg + ", keynb_sign=" + key_sign + ")"); byte[] signature = cc.cardComputeSign(key_sign, CM, JCconstants.MODE_SIGN, buffer, null); // 16 first bits for size System.out.println("Data length:" + buffer.length); System.out.println("signature after:" + toHexString(signature)); // computeverify System.out.println("\t Test ComputeVerify(CM=" + stralg + ", keynb=" + key_verif + ")"); byte[] response = cc.cardComputeSign(key_verif, CM, JCconstants.MODE_VERIFY, buffer, signature); System.out.println("\t Verify signature with wrong data:"); response = cc.cardComputeSign(key_verif, CM, JCconstants.MODE_VERIFY, buffer_wrong, signature); System.out.println("\n\n"); } /** * Test of cardComputeSha512 method, of class CardConnector. */ @Test public void testCardComputeSha512() throws Exception { System.out.println("cardComputeSha512"); ArrayList<byte[]> msg_list = new ArrayList<>(); ArrayList<String> hash_list = new ArrayList<>(); msg_list.add(new byte[0]); hash_list.add( "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"); msg_list.add(new byte[] { 'a', 'b', 'c' }); hash_list.add( "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"); for (int i = 0; i < msg_list.size(); i++) { byte[] response = null; try { response = cc.cardComputeSha512(msg_list.get(i)); assertArrayEquals(response, hash_list.get(i).getBytes()); //System.out.println("hash expected:"+hash_list.get(i)); //System.out.println("hash computed:"+toHexString(response)); } catch (CardConnectorException ex) { System.out.println( "CardConnectorException: " + ex.getMessage() + " " + Integer.toHexString(ex.getIns() & 0xff) + " " + Integer.toHexString(ex.getSW12() & 0xffff)); if (ex.getSW12() != 0x6d00) // sha512 may be unsupported (only for debugging) fail("Wrong sha512"); } } } /** * Test of cardComputeHmac method, of class CardConnector. */ @Test public void testCardComputeHmac() throws Exception { System.out.println("cardComputeHmac"); ArrayList<String> msg_list = new ArrayList<>(); ArrayList<String> key_list = new ArrayList<>(); key_list.add("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); key_list.add("4a656665"); msg_list.add("4869205468657265"); msg_list.add("7768617420646f2079612077616e7420666f72206e6f7468696e673f"); msg_list.add( "53616D706C6520233200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); for (int i = 0; i < msg_list.size(); i++) { for (int j = 0; j < key_list.size(); j++) { byte[] bmsg = DatatypeConverter.parseHexBinary(msg_list.get(i)); byte[] bkey = DatatypeConverter.parseHexBinary(key_list.get(j)); // sw byte[] hmac160_sw, hmac512_sw; // get an hmac_sha1 key from the raw key bytes SecretKeySpec key160 = new SecretKeySpec(bkey, "HmacSHA1"); SecretKeySpec key512 = new SecretKeySpec(bkey, "HmacSHA512"); // get an hmac_sha1 Mac instance and initialize with the signing key Mac mac160 = Mac.getInstance("HmacSHA1"); mac160.init(key160); Mac mac512 = Mac.getInstance("HmacSHA512"); mac512.init(key512); // compute the hmac on input data bytes hmac160_sw = mac160.doFinal(bmsg); hmac512_sw = mac512.doFinal(bmsg); //hw byte[] hmac160_hw = cc.cardComputeHmac((byte) 20, bkey, bmsg); byte[] hmac512_hw = cc.cardComputeHmac((byte) 64, bkey, bmsg); assertArrayEquals(hmac160_sw, hmac160_hw); assertArrayEquals(hmac512_sw, hmac512_hw); //System.out.println("hash expected:"+hmac_list.get(i)); //System.out.println("hash computed:"+toHexString(response)); } } // byte[] msg=DatatypeConverter.parseHexBinary("53616D706C6520233200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); // byte[] key= new byte[20]; // byte[] expResult = DatatypeConverter.parseHexBinary("C2462735FF0C2C7BE828E7947DADE721AB291BF5"); } /** * Test of cardCreatePIN method, of class CardConnector. */ @Test public void testCardPIN() throws Exception { System.out.println("cardCreatePIN"); byte[] response; try { response = cc.cardCreatePIN(pin2_nbr, pin2_tries, pin2, ublk2); } catch (CardConnectorException ex) { if (ex.getSW12() == JCconstants.SW_INCORRECT_P1) System.out.println("PIN exists already!"); else throw ex; } System.out.println("cardVerifyPIN"); response = cc.cardVerifyPIN(pin2_nbr, pin2); System.out.println("cardChangePIN"); byte[] new_pin = { 33, 33, 33, 33 }; response = cc.cardChangePIN(pin2_nbr, pin2, new_pin); System.out.println("cardVerifyPIN (new PIN)"); response = cc.cardVerifyPIN(pin2_nbr, new_pin); System.out.println("cardChangePIN (back to old PIN)"); response = cc.cardChangePIN(pin2_nbr, new_pin, pin2); } /** * Test of cardCreateObject method, of class CardConnector. */ @Test public void testCardCreateObject() throws Exception { System.out.println("cardCreateObject"); System.out.println("cardWriteObject"); System.out.println("cardReadObject"); System.out.println("cardDeleteObject"); byte[] objACL = DEFAULT_ACL;//{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; int objId = 123456; for (int size = 1; size < 65536; size = size * 2) { System.out.println("\t size:" + size); byte[] objData = new byte[size]; Arrays.fill(objData, (byte) 0x00); try { cc.cardCreateObject(objId, size, objACL); } catch (CardConnectorException ex) { if (ex.getSW12() == JCconstants.SW_OBJECT_EXISTS) { System.out.println("TestObject - delete existing object!"); cc.cardDeleteObject(objId, (byte) 0x01); } if (ex.getSW12() == JCconstants.SW_NO_MEMORY_LEFT) { System.out.println("TestObject - out of memory!"); //cc.cardDeleteObject(objId, (byte)0x01); return; } } cc.cardWriteObject(objId, objData); byte[] objCopy = cc.cardReadObject(objId); assertArrayEquals(objData, objCopy); int objSize = cc.cardGetObjectSize(objId); assertEquals(objSize, objCopy.length); cc.cardDeleteObject(objId, (byte) 0x01); } } /** * Test of cardGetStatus method, of class CardConnector. */ @Test public void testCardGetStatus() throws Exception { System.out.println("cardGetStatus"); byte[] response = cc.cardGetStatus(); CardDataParser.CardStatus parser = new CardDataParser.CardStatus(response); System.out.println(parser.toString()); } }