From ff9d53a07c5f13185e5fa64d4043370b727f25a0 Mon Sep 17 00:00:00 2001 From: Toporin Date: Mon, 15 Jun 2015 16:46:46 +0200 Subject: [PATCH] add test package, support for easy parsing of card response in CardDataParser rewrite TxParser --- pom.xml | 25 +- .../satochipclient/CardConnector.java | 721 +++++------ .../satochipclient/CardDataParser.java | 452 +++++++ .../satochipclient/SatoChipClient.java | 1152 +---------------- .../org/satochip/satochipclient/TxParser.java | 230 ++++ .../toporin/bitcoincore/ECDSASignature.java | 108 ++ .../org/toporin/bitcoincore/ECException.java | 42 + .../java/org/toporin/bitcoincore/ECKey.java | 473 +++++++ .../bitcoincore/SignatureException.java | 41 + .../java/org/toporin/bitcoincore/Utils.java | 571 ++++++++ .../java/org/toporin/bitcoincore/VarInt.java | 265 ++++ .../satochipclient/CardConnectorTest.java | 1056 +++++++++++++++ 12 files changed, 3592 insertions(+), 1544 deletions(-) create mode 100644 src/main/java/org/satochip/satochipclient/CardDataParser.java create mode 100644 src/main/java/org/satochip/satochipclient/TxParser.java create mode 100644 src/main/java/org/toporin/bitcoincore/ECDSASignature.java create mode 100644 src/main/java/org/toporin/bitcoincore/ECException.java create mode 100644 src/main/java/org/toporin/bitcoincore/ECKey.java create mode 100644 src/main/java/org/toporin/bitcoincore/SignatureException.java create mode 100644 src/main/java/org/toporin/bitcoincore/Utils.java create mode 100644 src/main/java/org/toporin/bitcoincore/VarInt.java create mode 100644 src/test/java/org/satochip/satochipclient/CardConnectorTest.java diff --git a/pom.xml b/pom.xml index fab0af0..bce5e39 100644 --- a/pom.xml +++ b/pom.xml @@ -11,16 +11,35 @@ bitcoinj 0.11.3 jar + test junit junit - 4.11 + 4.12 + test + + + org.toporin + yubikey4java + 0.1 + + + org.bouncycastle + bcprov-jdk15on + 1.51 + compile + + + org.slf4j + slf4j-api + 1.7.7 + jar UTF-8 - 1.7 - 1.7 + 1.8 + 1.8 \ No newline at end of file diff --git a/src/main/java/org/satochip/satochipclient/CardConnector.java b/src/main/java/org/satochip/satochipclient/CardConnector.java index 1dca6c7..45da8cc 100644 --- a/src/main/java/org/satochip/satochipclient/CardConnector.java +++ b/src/main/java/org/satochip/satochipclient/CardConnector.java @@ -25,6 +25,8 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.List; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; import javax.smartcardio.Card; @@ -35,13 +37,14 @@ import javax.smartcardio.ResponseAPDU; import javax.smartcardio.TerminalFactory; - /** * * @author Toporin */ public class CardConnector { + private final static Logger logger = Logger.getLogger(CardConnector.class.getName()); + /* constants declaration */ private Card card; private CardChannel channel; @@ -55,13 +58,22 @@ public class CardConnector { // constructor public CardConnector(){ + //default log data to console + this(new ConsoleHandler(), Level.INFO); + } + + public CardConnector(Handler handle, Level level){ + + //default log data + logger.addHandler(handle); + logger.setLevel(level); terminalfactory = TerminalFactory.getDefault(); try { listterminal = terminalfactory.terminals().list(); - System.out.println("Terminals: " + listterminal); + logger.log(Level.INFO, "Terminals: {0}", listterminal); if (listterminal.isEmpty()) { - System.out.println("No terminals found."); + logger.log(Level.SEVERE, "No terminals found."); return; } // Get the first terminal in the list @@ -72,13 +84,11 @@ public CardConnector(){ e.printStackTrace(); } - System.out.println("Card: " + card); ATR = card.getATR().getBytes(); - System.out.println("ATR: " + toString(ATR)); channel = card.getBasicChannel(); } - - public void disconect() throws CardException{ + + public void disconnect() throws CardException{ card.disconnect(true); } @@ -92,22 +102,22 @@ public byte[] getATR(){ */ public static String toString(byte[] bytes) { - if (bytes==null) - return "null"; - - final String hexChars = "0123456789ABCDEF"; - StringBuffer sbTmp = new StringBuffer(); - char[] cTmp = new char[2]; - - //System.out.println(bytes);//debug - for (int i = 0; i < bytes.length; i++) { - cTmp[0] = hexChars.charAt((bytes[i] & 0xF0) >>> 4); - cTmp[1] = hexChars.charAt(bytes[i] & 0x0F); - sbTmp.append(cTmp); - } - //System.out.println(sbTmp.toString());//debug + if (bytes==null) + return "null"; + + final String hexChars = "0123456789ABCDEF"; + StringBuffer sbTmp = new StringBuffer(); + char[] cTmp = new char[2]; + + //logger.log(Level.FINE,bytes);//debug + for (int i = 0; i < bytes.length; i++) { + cTmp[0] = hexChars.charAt((bytes[i] & 0xF0) >>> 4); + cTmp[1] = hexChars.charAt(bytes[i] & 0x0F); + sbTmp.append(cTmp); + } + //logger.log(Level.FINE,sbTmp.toString());//debug - return sbTmp.toString(); + return sbTmp.toString(); } // Exchange APDU with javacard @@ -119,9 +129,9 @@ public byte[] exchangeAPDU(byte cla, byte ins, byte p1, byte p2, byte[] data, by } catch (CardException ex) { throw new CardConnectorException("CardException during connection", c, r); } - System.out.println("SW12 <<<: " + Integer.toHexString(r.getSW1()&0xFF) + " " + Integer.toHexString(r.getSW2()&0xFF) ); sw12= (short)r.getSW(); if (sw12!=JCconstants.SW_OK){ + logger.log(Level.WARNING, "SW12 <<<: {0}", Integer.toHexString(sw12&0xFFFF)); throw new CardConnectorException("exchangeAPDU error", c, r); } return r.getData(); @@ -136,108 +146,104 @@ public ResponseAPDU getLastResponse(){ public CommandAPDU getLastCommand(){ return c; } - /* convert a DER encoded signature to compact 65-byte format - input is hex string in DER format - output is hex string in compact 65-byteformat - http://bitcoin.stackexchange.com/questions/12554/why-the-signature-is-always-65-13232-bytes-long - https://bitcointalk.org/index.php?topic=215205.0 - */ - public static byte[] toCompactSig(byte[] sigin, int recid, boolean compressed) { - - byte[] sigout= new byte[65]; - // parse input - byte first= sigin[0]; - if (first!= 0x30){ - System.out.println("Wrong first byte!"); - return new byte[0]; - } - byte lt= sigin[1]; - byte check= sigin[2]; - if (check!= 0x02){ - System.out.println("Check byte should be 0x02"); - return new byte[0]; - } - // extract r - byte lr= sigin[3]; - for (int i= 0; i<=31; i++){ - byte tmp= sigin[4+lr-1-i]; - if (lr>=(i+1)) { - sigout[32-i]= tmp; - } else{ - sigout[32-i]=0; - } - } - // extract s - check= sigin[4+lr]; - if (check!= 0x02){ - System.out.println("Second check byte should be 0x02"); - return new byte[0]; - } - byte ls= sigin[5+lr]; - if (lt != (lr+ls+4)){ - System.out.println("Wrong lt value"); - return new byte[0]; - } - for (int i= 0; i<=31; i++){ - byte tmp= sigin[5+lr+ls-i]; - if (ls>=(i+1)) { - sigout[64-i]= tmp; - } else{ - sigout[32-i]=0; - } - } - - // 1 byte header - if (recid>3 || recid<0){ - System.out.println("Wrong recid value"); - return new byte[0]; - } - if (compressed){ - sigout[0]= (byte)(27 + recid + 4 ); - }else{ - sigout[0]= (byte)(27 + recid); - } - - return sigout; + public void logCommandAPDU(String name, byte cla, byte ins, byte p1, byte p2, byte[] data, byte le){ + logger.log( + Level.FINE, + name+"\n\t APDU>> cla:{0} ins:{1} p1:{2} p2:{3} le:{4} data:{5}", + new Object[]{ + Integer.toHexString(cla & 0xFF), + Integer.toHexString(ins & 0xFF), + Integer.toHexString(p1 & 0xFF), + Integer.toHexString(p2 & 0xFF), + Integer.toHexString(le & 0xFF), + toString(data)} + ); + } + public void logResponseAPDU(byte[] response){ + if (response!=null && response.length>0) + logger.log(Level.FINE, "\t APDU<< {0}", toString(response)); } -// public static byte[] recoverPublicKeyFromSig(int recID, byte[] msg, byte[] sig, boolean doublehash){ -// -// //if (true) -// // throw new CardConnectorException("debug bitcoinj", (byte)0, (short)0 ); -// -// ECKey.ECDSASignature ecdsasig= toECDSASignature(sig); -// Sha256Hash msghash= Sha256Hash.create(msg); // compute sha256 of message -// if (doublehash){ -// msghash= Sha256Hash.create(msghash.getBytes()); +// /* convert a DER encoded signature to compact 65-byte format +// input is hex string in DER format +// output is hex string in compact 65-byteformat +// http://bitcoin.stackexchange.com/questions/12554/why-the-signature-is-always-65-13232-bytes-long +// https://bitcointalk.org/index.php?topic=215205.0 +// */ +// public static byte[] toCompactSig(byte[] sigin, int recid, boolean compressed) { +// +// byte[] sigout= new byte[65]; +// // parse input +// byte first= sigin[0]; +// if (first!= 0x30){ +// System.out.println("Wrong first byte!"); +// return new byte[0]; +// } +// byte lt= sigin[1]; +// byte check= sigin[2]; +// if (check!= 0x02){ +// System.out.println("Check byte should be 0x02"); +// return new byte[0]; +// } +// // extract r +// byte lr= sigin[3]; +// for (int i= 0; i<=31; i++){ +// byte tmp= sigin[4+lr-1-i]; +// if (lr>=(i+1)) { +// sigout[32-i]= tmp; +// } else{ +// sigout[32-i]=0; +// } +// } +// // extract s +// check= sigin[4+lr]; +// if (check!= 0x02){ +// System.out.println("Second check byte should be 0x02"); +// return new byte[0]; +// } +// byte ls= sigin[5+lr]; +// if (lt != (lr+ls+4)){ +// System.out.println("Wrong lt value"); +// return new byte[0]; +// } +// for (int i= 0; i<=31; i++){ +// byte tmp= sigin[5+lr+ls-i]; +// if (ls>=(i+1)) { +// sigout[64-i]= tmp; +// } else{ +// sigout[32-i]=0; +// } +// } +// +// // 1 byte header +// if (recid>3 || recid<0){ +// System.out.println("Wrong recid value"); +// return new byte[0]; +// } +// if (compressed){ +// sigout[0]= (byte)(27 + recid + 4 ); +// }else{ +// sigout[0]= (byte)(27 + recid); // } -// com.google.bitcoin.core.ECKey pkey= ECKey.recoverFromSignature(recID, ecdsasig, msghash, true); -// if (pkey!=null) -// return pkey.getPubKey(); -// else -// return null; -// +// +// return sigout; // } // SELECT Command public byte[] cardSelect(byte[] AID) throws CardConnectorException { - // See GlobalPlatform Card Specification (e.g. 2.2, section 11.9) - byte cla= 0x00; - byte ins= (byte)0xA4; - byte p1= 0x04; - byte p2=0x00; - byte le= 0x00; - System.out.println("CardSelect"); - System.out.println("APDU >>>: " + toString(AID)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, AID, le); - System.out.println("APDU <<<: " + toString(response)); - - return response; + // See GlobalPlatform Card Specification (e.g. 2.2, section 11.9) + byte cla= 0x00; + byte ins= (byte)0xA4; + byte p1= 0x04; + byte p2=0x00; + byte le= 0x00; + + byte[] response= exchangeAPDU(cla, ins, p1, p2, AID, le); + return response; } /** - * Card Setup (API not clear) + * Card Setup (todo: clarify API) * * **/ public byte[] cardSetup( @@ -246,7 +252,9 @@ public byte[] cardSetup( byte pin_tries_1, byte ublk_tries_1, byte[] pin_1, byte[] ublk_1, short memsize, short memsize2, - byte create_object_ACL, byte create_key_ACL, byte create_pin_ACL) throws CardConnectorException { + byte create_object_ACL, byte create_key_ACL, byte create_pin_ACL, + short option_flags, + byte[] hmacsha160_key, long amount_limit) throws CardConnectorException { // to do: check pin sizes < 256 byte[] pin={0x4D, 0x75, 0x73, 0x63, 0x6C, 0x65, 0x30, 0x30}; // default pin @@ -258,8 +266,11 @@ public byte[] cardSetup( // data=[pin_length(1) | pin | // pin_tries0(1) | ublk_tries0(1) | pin0_length(1) | pin0 | ublk0_length(1) | ublk0 | // pin_tries1(1) | ublk_tries1(1) | pin1_length(1) | pin1 | ublk1_length(1) | ublk1 | - // memsize(2) | memsize2(2) | ACL(3) ] - byte[] data= new byte[16+pin.length+pin_0.length+pin_1.length+ublk_0.length+ublk_1.length]; + // memsize(2) | memsize2(2) | ACL(3) | + // option_flags(2) | hmacsha160_key(20) | amount_limit(8)] + int optionsize= ((option_flags==0)?0:2) + (((option_flags&0x8000)==0x8000)?28:0); + int datasize= 16+pin.length+pin_0.length+pin_1.length+ublk_0.length+ublk_1.length+optionsize; + byte[] data= new byte[datasize]; byte le= 0x00; short base=0; //initial PIN check @@ -290,7 +301,7 @@ public byte[] cardSetup( for (int i=0; i>8); data[base++]= (byte)(memsize&0x00ff); // mem_size @@ -300,17 +311,42 @@ public byte[] cardSetup( data[base++]= create_object_ACL; data[base++]= create_key_ACL; data[base++]= create_pin_ACL; - - // send apdu - System.out.println("cardSetup"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response = null; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - return response; - + // option_flags + if (option_flags!=0){ + data[base++]= (byte)(option_flags>>8); + data[base++]= (byte)(option_flags&0x00ff); + // hmacsha1_key + System.arraycopy(hmacsha160_key, 0, data, base, 20); + base+=20; + // amount_limit + for (int i=56; i>=0; i-=8){ + data[base++]=(byte)((amount_limit>>i)&0xff); + } + } + // send apdu (contains sensitive data!) + byte[] response = exchangeAPDU(cla, ins, p1, p2, data, le); + return response; } + /** + * Card Setup (API not clear) + * + * **/ + public byte[] cardSetup( + byte pin_tries_0, byte ublk_tries_0, + byte[] pin_0, byte[] ublk_0, + byte pin_tries_1, byte ublk_tries_1, + byte[] pin_1, byte[] ublk_1, + short memsize, short memsize2, + byte create_object_ACL, byte create_key_ACL, byte create_pin_ACL) throws CardConnectorException { + + return cardSetup( + pin_tries_0, ublk_tries_0, pin_0, ublk_0, + pin_tries_1, ublk_tries_1, pin_1, ublk_1, + memsize, memsize2, create_object_ACL, create_key_ACL, create_pin_ACL, + (short)0, null, 0); + } + public byte[] cardBip32ImportSeed(byte[] keyACL, byte[] seed) throws CardConnectorException{ byte cla= JCconstants.CardEdge_CLA; @@ -326,13 +362,8 @@ public byte[] cardBip32ImportSeed(byte[] keyACL, byte[] seed) throws CardConnect data[base++]= (byte)seed.length; System.arraycopy(seed, 0, data, base, seed.length); - // send apdu - System.out.println("ImportBIP32Seed"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + // send apdu (contains sensitive data!) + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); return response; } @@ -347,12 +378,9 @@ public byte[] cardBip32GetAuthentiKey() throws CardConnectorException{ short base=0; // send apdu - System.out.println("GetBip32AuthentiKey"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + logCommandAPDU("cardBip32GetAuthentiKey", cla, ins, p1, p2, data, le); + byte[] response = exchangeAPDU(cla, ins, p1, p2, data, le); + logResponseAPDU(response); return response; } @@ -370,18 +398,20 @@ public byte[] cardBip32GetExtendedKey(byte[] path) throws CardConnectorException base+=path.length; // send apdu - System.out.println("GetBip32ExtendedKey"); - System.out.println("APDU >>>: " + toString(data)); byte[] response=null; try{ + // send apdu + logCommandAPDU("GetBip32ExtendedKey",cla, ins, p1, p2, data, le); response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); + logResponseAPDU(response); } catch(CardConnectorException ex){ // if there is no more memory available, erase cache... if (ex.getIns()==JCconstants.INS_BIP32_GET_EXTENDED_KEY && ex.getSW12()==JCconstants.SW_NO_MEMORY_LEFT){ - System.out.println("GetBip32ExtendedKey - out of memory: reset internal memory"); + logger.log(Level.INFO,"GetBip32ExtendedKey - out of memory: reset internal memory"); + logCommandAPDU("GetBip32ExtendedKey-reset",cla, ins, p1, p2, data, le); response = exchangeAPDU(cla, ins, p1, (byte)0xFF, data, le); + logResponseAPDU(response); } else{ throw ex; @@ -413,12 +443,10 @@ public byte[] cardSignMessage(byte keynbr, byte[] message) throws CardConnectorE data[base++]=(byte) ((buffer_left) & 0xff); // send apdu - System.out.println("cardSignBIP32Message - INIT"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + logCommandAPDU("cardSignBIP32Message - INIT",cla, ins, p1, p2, data, le); + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); + logResponseAPDU(response); + // CIPHER PROCESS/UPDATE (optionnal) while(buffer_left>chunk){ @@ -436,16 +464,14 @@ public byte[] cardSignMessage(byte keynbr, byte[] message) throws CardConnectorE buffer_left-=chunk; // send apdu - System.out.println("cardSignBIP32Message - PROCESS"); - System.out.println("APDU data >>>: " + toString(data)); - System.out.println("APDU datasize >>>: " + data.length); + logCommandAPDU("cardSignBIP32Message - PROCESS",cla, ins, p1, p2, data, le); response= exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); + logResponseAPDU(response); } // CIPHER FINAL/SIGN (last chunk) chunk= buffer_left; //following while condition, buffer_left<=chunk - System.out.println("chunk value= " + chunk); + logger.log(Level.FINE, "chunk value= {0}", chunk); //cla= JCconstants.CardEdge_CLA; //ins= INS_COMPUTE_CRYPT; //p1= key_nbr; @@ -460,11 +486,9 @@ public byte[] cardSignMessage(byte keynbr, byte[] message) throws CardConnectorE buffer_left-=chunk; // send apdu - System.out.println("cardSignBIP32Message - FINALIZE"); - System.out.println("APDU >>>: " + toString(data)); + logCommandAPDU("cardSignBIP32Message-FINALIZE",cla, ins, p1, p2, data, le); response= exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + logResponseAPDU(response); return response; } @@ -487,11 +511,9 @@ public byte[] cardSignShortMessage(byte keynbr, byte[] message) throws CardConne base+=message.length; // send apdu - System.out.println("SignShortBip32Message:"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); + logCommandAPDU("SignShortBip32Message",cla, ins, p1, p2, data, le); + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); + logResponseAPDU(response); return response; } @@ -526,25 +548,23 @@ public byte[] cardParseTransaction(byte[] transaction) throws CardConnectorExcep result= TransactionParser.parseTransaction(transaction); if (result== TransactionParser.RESULT_ERROR){ - System.out.println("Error during parsing"); + logger.log(Level.WARNING,"Error during parsing"); return null; } data= TransactionParser.getDataChunk(); digestFull.update(data, 0, data.length); - // send apdu + // log state & send apdu if (result== TransactionParser.RESULT_FINISHED){ le= 52; // [nb_input(4) | nb_output(4) | coord_actif_input(4) | amount(8) | hash(32) | sig?] - System.out.println("cardParseTransaction - FINISH"); + logCommandAPDU("cardParseTransaction-FINISH",cla, ins, p1, p2, data, le); } else if (p1== JCconstants.OP_INIT) - System.out.println("cardParseTransaction - INIT"); + logCommandAPDU("cardParseTransaction-INIT",cla, ins, p1, p2, data, le); else if (p1== JCconstants.OP_PROCESS) - System.out.println("cardParseTransaction - PROCESS"); - System.out.println("APDU >>>: " + toString(data)); + logCommandAPDU("cardParseTransaction-PROCESS",cla, ins, p1, p2, data, le); response= exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - Logger.getLogger(CardConnector.class.getName()).log(Level.SEVERE, "cardParseTranaction: while apdu response:"+CardConnector.toString(response)); + logResponseAPDU(response); // switch to process mode after initial call to parse p1= JCconstants.OP_PROCESS; @@ -577,6 +597,43 @@ else if (p1== JCconstants.OP_PROCESS) return response; } + public byte[] cardParseTx(byte[] transaction) throws CardConnectorException{ + + byte cla= JCconstants.CardEdge_CLA; + byte ins= JCconstants.INS_PARSE_TRANSACTION; + byte p1= JCconstants.OP_INIT; + byte p2= 0x00; + byte[] data; + byte le= 0x00; + byte[] response=null; + + // init transaction data and context + TxParser txparser= new TxParser(transaction); + while(!txparser.isParsed()){ + + data= txparser.parseTransaction(); + + // log state & send apdu + if (txparser.isParsed()){ + le= 86; // [hash(32) | sigsize(2) | sig | nb_input(4) | nb_output(4) | coord_actif_input(4) | amount(8)] + logCommandAPDU("cardParseTransaction - FINISH",cla, ins, p1, p2, data, le); + } + else if (p1== JCconstants.OP_INIT) + logCommandAPDU("cardParseTransaction-INIT",cla, ins, p1, p2, data, le); + else if (p1== JCconstants.OP_PROCESS) + logCommandAPDU("cardParseTransaction - PROCESS",cla, ins, p1, p2, data, le); + response= exchangeAPDU(cla, ins, p1, p2, data, le); + logResponseAPDU(response); + + // switch to process mode after initial call to parse + p1= JCconstants.OP_PROCESS; + } + logger.log(Level.INFO, "Single transaction hash:{0}", toString(txparser.getTxHash())); + logger.log(Level.INFO, "Double transaction hash:{0}", toString(txparser.getTxDoubleHash())); + + return response; + } + public byte[] cardSignTransaction(byte keynbr, byte[] txhash, byte[] chalresponse) throws CardConnectorException{ byte cla= JCconstants.CardEdge_CLA; @@ -598,11 +655,9 @@ public byte[] cardSignTransaction(byte keynbr, byte[] txhash, byte[] chalrespons System.arraycopy(chalresponse, 0, data, txhash.length, chalresponse.length); } - System.out.println("cardSignTransaction"); - System.out.println("APDU >>>: " + toString(data)); + logCommandAPDU("cardSignTransaction",cla, ins, p1, p2, data, le); response= exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + logResponseAPDU(response); return response; } @@ -611,7 +666,7 @@ public byte[] cardImportKey( byte key_encoding, byte key_type, short key_size, byte[] key_blob) throws CardConnectorException{ if (key_blob.length>242){ - System.out.println("Invalid data size (>242)"); + logger.log(Level.WARNING,"Invalid data size (>242)"); return null; } @@ -634,12 +689,9 @@ public byte[] cardImportKey( base+=key_blob.length; // import key command (data taken from imported object) - System.out.println("cardImportKey"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + logCommandAPDU("cardImportKey",cla, ins, p1, p2, null, le); + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); + logResponseAPDU(response); return response; } @@ -653,12 +705,9 @@ public byte[] cardGetPublicKeyFromPrivate(byte priv_key_nbr) throws CardConnecto byte le= 0x00; // send apdu - System.out.println("cardGetPublicKeyFromPrivate"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + logCommandAPDU("cardGetPublicKeyFromPrivate", cla, ins, p1, p2, data, le); + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); + logResponseAPDU(response); return response; } @@ -691,11 +740,9 @@ public byte[] cardGenerateKeyPair( base+=gen_opt_param.length; // send apdu - System.out.println("cardGenKeyPair"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); + logCommandAPDU("cardGenKeyPair",cla, ins, p1, p2, data, le); + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); + logResponseAPDU(response); return response; } @@ -721,11 +768,9 @@ public byte[] cardGenerateSymmetricKey( } // send apdu - System.out.println("cardGenSymmetricKey"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); + logCommandAPDU("cardGenSymmetricKey",cla, ins, p1, p2, data, le); + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); + logResponseAPDU(response); return response; } @@ -752,12 +797,10 @@ public byte[] cardComputeSign(byte key_nbr, byte CM, byte CD, byte[] buffer, byt data[base++]=(byte) 0; // size!=0 for DES macing with IV // send apdu - System.out.println("cardComputeSign - INIT"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + logCommandAPDU("cardComputeSign-INIT",cla, ins, p1, p2, data, le); + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); + logResponseAPDU(response); + // CIPHER PROCESS/UPDATE (optionnal) while(buffer_left>chunk){ @@ -776,17 +819,15 @@ public byte[] cardComputeSign(byte key_nbr, byte CM, byte CD, byte[] buffer, byt buffer_left-=chunk; // send apdu - System.out.println("cardComputeSign - PROCESS"); - System.out.println("APDU data >>>: " + toString(data)); - System.out.println("APDU datasize >>>: " + data.length); + logCommandAPDU("cardComputeSign-PROCESS",cla, ins, p1, p2, data, le); response= exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); + logResponseAPDU(response); } // CIPHER FINAL/SIGN (last chunk) if (CD == JCconstants.MODE_SIGN){ chunk= buffer_left; //following while condition, buffer_left<=chunk - System.out.println("chunk value= " + chunk); + logger.log(Level.FINE, "chunk value= {0}", chunk); //cla= JCconstants.CardEdge_CLA; //ins= INS_COMPUTE_CRYPT; //p1= key_nbr; @@ -802,10 +843,9 @@ public byte[] cardComputeSign(byte key_nbr, byte CM, byte CD, byte[] buffer, byt buffer_left-=chunk; // send apdu - System.out.println("cardComputeSign - FINALIZE"); - System.out.println("APDU >>>: " + toString(data)); + logCommandAPDU("cardComputeSign-FINALIZE",cla, ins, p1, p2, data, le); response= exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); + logResponseAPDU(response); //signature= new byte[response.length-2]; // datachunk is 1 short + data //System.arraycopy(response, 2, signature, 0, response.length-2); return response; @@ -832,12 +872,9 @@ public byte[] cardComputeSign(byte key_nbr, byte CM, byte CD, byte[] buffer, byt base+=sign_length; // send apdu - System.out.println("cardComputeVerify - FINALIZE"); - System.out.println("APDU >>>: " + toString(data)); - System.out.println("LE= " + le);//debug - System.out.println("data length= " + data.length);//debug + logCommandAPDU("cardComputeVerify-FINALIZE",cla, ins, p1, p2, data, le); response= exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); + logResponseAPDU(response); } return response; @@ -871,11 +908,11 @@ else if (CM==JCconstants.ALG_AES_BLOCK_128_CBC_NOPAD || byte[] paddedbuffer= new byte[paddedlength]; Arrays.fill(paddedbuffer, (byte)paddinglength); System.arraycopy(buffer, 0, paddedbuffer, 0, buffer.length); -// System.out.println("PADDING:"); -// System.out.println("length:"+buffer.length); -// System.out.println("paddedlength:"+paddedlength); -// System.out.println("paddinglength:"+paddinglength); -// System.out.println("paddedbuffer:"+toString(paddedbuffer)); +// logger.log(Level.FINE,"PADDING:"); +// logger.log(Level.FINE,"length:"+buffer.length); +// logger.log(Level.FINE,"paddedlength:"+paddedlength); +// logger.log(Level.FINE,"paddinglength:"+paddinglength); +// logger.log(Level.FINE,"paddedbuffer:"+toString(paddedbuffer)); buffer=paddedbuffer; } @@ -911,15 +948,13 @@ else if (CM==JCconstants.ALG_AES_BLOCK_128_CBC_NOPAD) bufferLeft-=IVlength; // send apdu - System.out.println("cardComputeCrypt - INIT"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); + logCommandAPDU("cardComputeCrypt-INIT",cla, ins, p1, p2, data, le); + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); + logResponseAPDU(response); // recover IV from card (for CD=ENCRYPT && CM=CBC) ByteArrayOutputStream bos= new ByteArrayOutputStream(buffer.length+IVlength); bos.write(response,0,response.length); - System.out.println("IV: "+ toString(response)); + logResponseAPDU(response); // CIPHER PROCESS/UPDATE (optionnal) while(bufferLeft>chunk){ @@ -938,19 +973,17 @@ else if (CM==JCconstants.ALG_AES_BLOCK_128_CBC_NOPAD) bufferLeft-=chunk; // send apdu - System.out.println("cardComputeCrypt - PROCESS"); - System.out.println("APDU data >>>: " + toString(data)); - System.out.println("APDU datasize >>>: " + data.length); + logCommandAPDU("cardComputeCrypt - PROCESS",cla, ins, p1, p2, data, le); response= exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + logResponseAPDU(response); + // update output bos.write(response,2,response.length-2); } // CIPHER FINAL (last chunk) chunk= bufferLeft; //following while condition, buffer_left<=chunk - System.out.println("chunk value= " + chunk); + logger.log(Level.FINE, "chunk value= {0}", chunk); //cla= JCconstants.CardEdge_CLA; //ins= INS_COMPUTE_CRYPT; //p1= key_nbr; @@ -966,10 +999,9 @@ else if (CM==JCconstants.ALG_AES_BLOCK_128_CBC_NOPAD) bufferLeft-=chunk; // send apdu - System.out.println("cardComputeCrypt - FINALIZE"); - System.out.println("APDU >>>: " + toString(data)); + logCommandAPDU("cardComputeCrypt-FINALIZE",cla, ins, p1, p2, data, le); response= exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); + logResponseAPDU(response); // update output bos.write(response,2,response.length-2); @@ -983,10 +1015,10 @@ else if (CM==JCconstants.ALG_AES_BLOCK_128_CBC_NOPAD) int paddinglength= paddedoutput[paddedoutput.length-1]; output= new byte[paddedoutput.length-paddinglength]; System.arraycopy(paddedoutput, 0, output, 0, output.length); -// System.out.println("UNPADDING"); -// System.out.println("paddedlength:"+paddedoutput.length); -// System.out.println("paddinglength:"+paddinglength); -// System.out.println("paddedoutput:"+toString(paddedoutput)); +// logger.log(Level.FINE,"UNPADDING"); +// logger.log(Level.FINE,"paddedlength:"+paddedoutput.length); +// logger.log(Level.FINE,"paddinglength:"+paddinglength); +// logger.log(Level.FINE,"paddedoutput:"+toString(paddedoutput)); } else{ output= bos.toByteArray(); @@ -1008,20 +1040,17 @@ public byte[] cardComputeSha512(byte[] msg) throws CardConnectorException{ data[base++]=msg[i]; } - System.out.println("cardComputeSha512"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + logCommandAPDU("cardComputeSha512",cla, ins, p1, p2, data, le); + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); + logResponseAPDU(response); return response; } - - public byte[] cardComputeHmacSha512(byte[] key, byte[] msg) throws CardConnectorException{ + + public byte[] cardComputeHmac(byte sha, byte[] key, byte[] msg) throws CardConnectorException{ byte cla= JCconstants.CardEdge_CLA; byte ins= JCconstants.INS_COMPUTE_HMACSHA512; - byte p1= 0x00; + byte p1= sha; byte p2= 0x00; byte[] data= new byte[key.length+msg.length+4]; byte le= 64; @@ -1036,15 +1065,15 @@ public byte[] cardComputeHmacSha512(byte[] key, byte[] msg) throws CardConnector for (int i=0; i>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + + logCommandAPDU("cardComputeHmac",cla, ins, p1, p2, data, le); + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); + logResponseAPDU(response); return response; } - + + /* PIN Management*/ + public byte[] cardCreatePIN(byte pin_nbr, byte pin_tries, byte[] pin, byte[] ublk) throws CardConnectorException{ byte cla= JCconstants.CardEdge_CLA; @@ -1063,12 +1092,8 @@ public byte[] cardCreatePIN(byte pin_nbr, byte pin_tries, byte[] pin, byte[] ubl data[base++]=ublk[i]; } // send apdu - System.out.println("cardCreatePIN"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + logCommandAPDU("cardCreatPIN",cla, ins, p1, p2, null, le); + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); return response; } @@ -1085,12 +1110,8 @@ public byte[] cardVerifyPIN(byte pin_nbr, byte[] pin) throws CardConnectorExcept data[base++]=pin[i]; } // send apdu - System.out.println("cardVerifyPIN"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + logCommandAPDU("cardVerifyPIN",cla, ins, p1, p2, null, le); + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); return response; } @@ -1112,13 +1133,9 @@ public byte[] cardChangePIN(byte pin_nbr, byte[] old_pin, byte[] new_pin) throws data[base++]=new_pin[i]; } // send apdu - System.out.println("cardChangePIN"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - System.out.println("SW12 <<<: " + Integer.toHexString(response[response.length-2]&0xFF) + " " + Integer.toHexString(response[response.length-1]&0xFF) ); - + logCommandAPDU("cardChangePIN",cla, ins, p1, p2, null, le); + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); + return response; } @@ -1135,13 +1152,8 @@ public byte[] cardUnblockPIN(byte pin_nbr, byte[] ublk) throws CardConnectorExce data[base++]=ublk[i]; } // send apdu - System.out.println("cardUnblockPIN"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - System.out.println("SW12 <<<: " + Integer.toHexString(response[response.length-2]&0xFF) + " " + Integer.toHexString(response[response.length-1]&0xFF) ); - + logCommandAPDU("cardUnblockPIN",cla, ins, p1, p2, null, le); + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); return response; } @@ -1155,13 +1167,9 @@ public byte[] cardLogoutAll() throws CardConnectorException{ byte le= 0x00; // send apdu - System.out.println("cardLogoutAll"); - System.out.println("APDU >>>: " + toString(data)); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - System.out.println("SW12 <<<: " + Integer.toHexString(response[response.length-2]&0xFF) + " " + Integer.toHexString(response[response.length-1]&0xFF) ); - + logCommandAPDU("cardLogoutAll",cla, ins, p1, p2, data, le); + byte[] response= exchangeAPDU(cla, ins, p1, p2, data, le); + logResponseAPDU(response); return response; } @@ -1175,71 +1183,41 @@ public byte[] cardListPINs() throws CardConnectorException{ byte le= 0x02; // send apdu - System.out.println("cardCreatePIN"); - System.out.println("APDU >>>: "); + logger.log(Level.FINE,"cardListPIN"); + logger.log(Level.FINE,"APDU >>>: "); byte[] response; response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("Data <<<: " + response[0] + response[0]); - System.out.println("APDU <<<: " + toString(response)); - System.out.println("SW12 <<<: " + Integer.toHexString(response[response.length-2]&0xFF) + " " + Integer.toHexString(response[response.length-1]&0xFF) ); - + logResponseAPDU(response); return response; } public byte[] cardListKeys() throws CardConnectorException{ - byte seq_opt=0x00; byte cla= JCconstants.CardEdge_CLA; byte ins= JCconstants.INS_LIST_KEYS; - byte p1= seq_opt; + byte p1= 0x00; // "get first entry" option byte p2= 0x00; byte[] data= null; byte le=0x0B; // 11 bytes expected? int datasize= 0; byte[] response; - System.out.println("cardListKeys"); - System.out.println("APDU >>>: "); - + ByteArrayOutputStream baos= new ByteArrayOutputStream(200); + do{ - p1=seq_opt; - response= exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - // key info - datasize= response.length; - short base=0; - if (datasize>0){// datasize==11 - byte key_nbr= response[base++]; - byte key_type= response[base++]; - byte key_partner= response[base++]; - short key_size= (short) (((short)response[base++])<<8 + ((short)response[base++])); // to check order? - int[] key_ACL= new int[JCconstants.KEY_ACL_SIZE]; - for (short i= 0; i0); // while there are key entries - return response; + return baos.toByteArray(); } /** * This function creates an object that will be identified by the provided object ID. - * The object’s space and name will be allocated until deleted using MSCDeleteObject. + * The object’s space and name will be allocated until deleted using MSCDeleteObject. * The object will be allocated upon the card's memory heap. * Object creation is only allowed if the object ID is available and logged in * identity(-ies) have sufficient privileges to create objects. @@ -1272,18 +1250,16 @@ public byte[] cardCreateObject(int objId, int objSize, byte[] objACL) throws Car System.arraycopy(objACL, 0, data, 8, JCconstants.KEY_ACL_SIZE); // send apdu - System.out.println("cardCreateObject"); - System.out.println("APDU >>>: "); + logCommandAPDU("cardCreateObject",cla, ins, p1, p2, data, le); byte[] response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + logResponseAPDU(response); return response; } /** - * This function deletes the object identified by the provided object ID. The object’s + * This function deletes the object identified by the provided object ID. The object’s * space and name will be removed from the heap and made available for other objects. - * The zero flag denotes whether the object’s memory should be zeroed after + * The zero flag denotes whether the object’s memory should be zeroed after * deletion. This kind of deletion is recommended if object was storing sensitive data. * * ins: 0x52 @@ -1308,11 +1284,9 @@ public byte[] cardDeleteObject(int objId, byte secureErasure) throws CardConnect data[offset++]= (byte)((objId) & 0xff); // send apdu - System.out.println("cardDeleteObject"); - System.out.println("APDU >>>: "); + logCommandAPDU("cardDeleteObject",cla, ins, p1, p2, data, le); byte[] response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + logResponseAPDU(response); return response; } @@ -1320,7 +1294,7 @@ public byte[] cardDeleteObject(int objId, byte secureErasure) throws CardConnect * This function (over-)writes data to an object that has been previously created with * CreateObject. Provided Object Data is stored starting from the byte specified * by the Offset parameter. The size of provided object data must be exactly (Data - * Length – 8) bytes. Provided offset value plus the size of provided Object Data + * Length – 8) bytes. Provided offset value plus the size of provided Object Data * must not exceed object size. * Up to 246 bytes can be transferred with a single APDU. If more bytes need to be * transferred, then multiple WriteObject commands must be used with different offsets. @@ -1370,11 +1344,9 @@ public byte[] cardWriteObject(int objId, byte[] objData, int objOffset, int objL System.arraycopy(objData, objOffset, data, offset, objLength); // send apdu - System.out.println("cardWriteObject-offset:"+objOffset); - System.out.println("APDU >>>: "); + logCommandAPDU("cardWriteObject-offset",cla, ins, p1, p2, null, le); byte[] response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + logResponseAPDU(response); return response; } @@ -1385,7 +1357,7 @@ public byte[] cardWriteObject(int objId, byte[] objData, int objOffset, int objL * If more bytes need to be transferred, then multiple ReadObject commands must * be used with different offsets. * Object data will be effectively read only if logged in identity(ies) have - * sufficient privileges for the operation, according to the object’s ACL. + * sufficient privileges for the operation, according to the object’s ACL. * * ins: 0x56 * p1: 0x00 @@ -1438,11 +1410,9 @@ public byte[] cardReadObject(int objId, int objOffset, int objLength) throws Car data[offset++]= (byte) objLength; // send apdu - System.out.println("cardReadObject-offset:"+objOffset); - System.out.println("APDU >>>: "); + logCommandAPDU("cardReadObject-offset",cla, ins, p1, p2, data, le); byte[] response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - + logResponseAPDU(response); return response; } @@ -1472,12 +1442,10 @@ public short cardGetObjectSize(int objId) throws CardConnectorException{ data[offset++]= (byte)((objId) & 0xff); // send apdu - System.out.println("cardGetObjectSize:"); - System.out.println("APDU >>>: "); + logCommandAPDU("cardGetObjectSize",cla, ins, p1, p2, data, le); byte[] response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); + logResponseAPDU(response); short objSize= (short) (((response[0]&0xff)<<8)+(response[1]&0xff)); - return objSize; } @@ -1491,42 +1459,9 @@ public byte[] cardGetStatus() throws CardConnectorException{ byte le= 0x10; // 16 bytes expected? // send apdu - System.out.println("cardGetStatus"); - System.out.println("APDU >>>: "); - byte[] response; - response = exchangeAPDU(cla, ins, p1, p2, data, le); - System.out.println("APDU <<<: " + toString(response)); - - // key info - int datasize= response.length; - short base=0; - // process response (6 bytes - 2 long - 1 short) - if (datasize>0){// datasize ==15// 16? - byte CE_version_maj= response[base++]; - byte CE_version_min= response[base++]; - byte soft_version_maj= response[base++]; - byte soft_version_min= response[base++]; - int sec_mem_tot= ((response[base++]&0xff)<<8)+(response[base++]&0xff); - int mem_tot= ((response[base++]&0xff)<<8)+(response[base++]&0xff); - int sec_mem_free= ((response[base++]&0xff)<<8)+(response[base++]&0xff); - int mem_free= ((response[base++]&0xff)<<8)+(response[base++]&0xff); - byte PINs_nbr= response[base++]; - byte keys_nbr= response[base++]; - short logged_in= (short) (((response[base++]&0xff)<<8)+(response[base++]&0xff)); - System.out.println(" datasize(15?) <<<: " + datasize ); - System.out.println(" card Edge major version: "+CE_version_maj); - System.out.println(" card Edge minor version: "+CE_version_min); - System.out.println(" Applet major version: "+soft_version_maj); - System.out.println(" Applet minor version: "+soft_version_min); - System.out.println(" Total secure memory: "+ sec_mem_tot); - System.out.println(" Total object memory: "+ mem_tot); - System.out.println(" Free secure memory: "+ sec_mem_free); - System.out.println(" Free object memory: "+ mem_free); - System.out.println(" Number of used PIN: "+ PINs_nbr); - System.out.println(" Number of used keys: "+ keys_nbr); - System.out.println(" Currently logged in identities: "+ logged_in + " " + Integer.toBinaryString(logged_in)); - } - + logCommandAPDU("cardGetStatus",cla, ins, p1, p2, data, le); + byte[] response = exchangeAPDU(cla, ins, p1, p2, data, le); + logResponseAPDU(response); return response; } diff --git a/src/main/java/org/satochip/satochipclient/CardDataParser.java b/src/main/java/org/satochip/satochipclient/CardDataParser.java new file mode 100644 index 0000000..6fc0f4d --- /dev/null +++ b/src/main/java/org/satochip/satochipclient/CardDataParser.java @@ -0,0 +1,452 @@ +/* + * 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 java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.toporin.bitcoincore.ECException; +import org.toporin.bitcoincore.ECKey; +import org.toporin.bitcoincore.VarInt; + + +public class CardDataParser { + + + public static class PubKeyData{ + + //private static final byte[] BITCOIN_SIGNED_MESSAGE_HEADER = {0x18,'B','i','t','c','o','i','n',' ','S','i','g','n','e','d',' ','M','e','s','s','a','g','e',':','\n'}; //"Bitcoin Signed Message:\n"; + protected static final String BITCOIN_SIGNED_MESSAGE_HEADER = "Bitcoin Signed Message:\n"; + + private static final boolean DOUBLESHA256=true; + private static final boolean SINGLESHA256=false; + private static final int OPTION_FLAGS_MASK= 0x8000; + + public byte[] msg; + public byte[] data; + public byte[] signature; + public byte[] pubkey; + public byte[] coordx; + public byte[] authentikey; + public byte[] authentikey_coordx; + + public int msg_size; + public int data_size; + public int sig_size; + public int pubkey_recid; + public int authentikey_recid; + public int option_flags; + + public byte[] msg2; + public byte[] signature2; + public int msg2_size; + public int sig2_size; + + public byte[] compactsig; + public byte[] compactsig_b64; + public String compactsig_b64_str; + + public PubKeyData(){ + } + public PubKeyData(byte[] authkey){ + setAuthentikey(authkey); + } + public void setAuthentikey(byte[] authkey){ + authentikey= Arrays.copyOf(authkey, authkey.length); + authentikey_coordx= Arrays.copyOfRange(authentikey, 1, 1+32); + } + + public PubKeyData parseBip32GetAuthentikey(byte[] response) throws ECException{ + // self-signed authentikey: data= coordx + authentikey= parseSelfSignedData(response).pubkey; + authentikey_coordx= Arrays.copyOfRange(authentikey, 1, 1+32); + return this; + } + public PubKeyData parseBip32ImportSeed(byte[] response) throws ECException{ + // self-signed authentikey: data= coordx + parseBip32GetAuthentikey(response); + int offset=2+data_size+2+sig_size; + int nb_deleted = ((int)(response[offset] & 0xff)<<8) + ((int)(response[offset+1] & 0xff)); + + return this; + } + + public PubKeyData parseBip32GetExtendedKey(byte[] response) throws ECException{ + + if (authentikey==null) + throw new ECException("Authentikey not set"); + + // double signature: first is self-signed, second by authentikey + // firs self-signed sig: data= coordx + parseSelfSignedData(response); + + // second signature by authentikey + msg2_size= msg_size+2+sig_size; + msg2= Arrays.copyOfRange(response, 0, msg2_size); + sig2_size = ((int)(response[msg2_size] & 0xff)<<8) + ((int)(response[msg2_size+1] & 0xff)); + signature2= Arrays.copyOfRange(response, msg2_size+2, msg2_size+2+sig2_size); + ECKey.recoverFromSignature(authentikey_coordx, msg2, signature2, SINGLESHA256); + + return this; + } + + public PubKeyData parseGetPublicKeyFromPrivate(byte[] response) throws ECException{ + // self-signed: data= coordx + return parseSelfSignedData(response); + } + + public PubKeyData parseSelfSignedData(byte[] response) throws ECException{ + + // response= [data_size | data | sig_size | signature] + data_size = ((int)(response[0] & 0xff)<<8) + ((int)(response[1] & 0xff)); + data= Arrays.copyOfRange(response, 2, 2+data_size); + + msg_size= 2+data_size; + msg= Arrays.copyOfRange(response, 0, msg_size); + sig_size = ((int)(response[msg_size] & 0xff)<<8) + ((int)(response[msg_size+1] & 0xff)); + signature= Arrays.copyOfRange(response, msg_size+2, msg_size+2+sig_size); + + if (sig_size==0) + throw new ECException("Signature missing"); + // self-signed + pubkey= ECKey.recoverFromSignature(data, msg, signature, SINGLESHA256); + + return this; + } + + public PubKeyData parseMaybeSignedDataSafe(byte[] response) throws ECException{ + + // if signed, data is signed by authentikey! + // response= [data_size | data | sig_size | authentikey_signature] + data_size = ((int)(response[0] & 0xff)<<8) + ((int)(response[1] & 0xff)); + option_flags= (data_size & OPTION_FLAGS_MASK); + data_size &= ~OPTION_FLAGS_MASK; + data= Arrays.copyOfRange(response, 2, 2+data_size); + + msg_size= 2+data_size; + msg= Arrays.copyOfRange(response, 0, msg_size); + sig_size = ((int)(response[msg_size] & 0xff)<<8) + ((int)(response[msg_size+1] & 0xff)); + signature= Arrays.copyOfRange(response, msg_size+2, msg_size+2+sig_size); + + return this; + + } + public PubKeyData parseMaybeSignedData(byte[] response) throws ECException{ + + parseMaybeSignedDataSafe(response); + + // check signature with provided key or using data as coordx by default + if (sig_size==0) + return this; + else if (authentikey==null) + throw new ECException("Authentikey not set"); + pubkey= ECKey.recoverFromSignature(authentikey_coordx, msg, signature, SINGLESHA256); + + return this; + } + public PubKeyData parseTxHash(byte[] response) throws ECException{ + // hash signed by authentikey + return parseMaybeSignedData(response);// coordx+key self-signature + } + + + public PubKeyData parseMessageSigning(byte[] signature, byte[] sigpubkey, String message) throws CardDataParserException, ECException{ + + // Prepend the message for signing as done inside the card!! + //byte[] contents; + byte[] paddedcontents=null; + try (ByteArrayOutputStream outStream = new ByteArrayOutputStream(message.length()*2)) { + byte[] headerBytes = BITCOIN_SIGNED_MESSAGE_HEADER.getBytes("UTF-8"); + outStream.write(VarInt.encode(headerBytes.length)); + outStream.write(headerBytes); + byte[] messageBytes = message.getBytes("UTF-8"); + outStream.write(VarInt.encode(messageBytes.length)); + outStream.write(messageBytes); + paddedcontents = outStream.toByteArray(); + //contents = message.getBytes("UTF-8"); + } catch (UnsupportedEncodingException ex) { + Logger.getLogger(CardDataParser.class.getName()).log(Level.SEVERE, null, ex); + } catch (IOException ex) { + Logger.getLogger(CardDataParser.class.getName()).log(Level.SEVERE, null, ex); + } + + this.signature=signature; + byte[] sigpubkey_coordx= Arrays.copyOfRange(sigpubkey, 1, 1+32); + pubkey= ECKey.recoverFromSignature(sigpubkey_coordx, paddedcontents, signature, DOUBLESHA256); + pubkey_recid= ECKey.recidFromSignature(sigpubkey_coordx, paddedcontents, signature, DOUBLESHA256); + + compactsig= parseToCompactSig(signature, pubkey_recid, true); + //int recid=0; + //System.out.println("recid="+recid+ " pubkey_recid:"+pubkey_recid); + //compactsig= parseToCompactSig(signature, recid, true);//debug + + compactsig_b64= Base64.getEncoder().encode(compactsig); + compactsig_b64_str= null; + try { + compactsig_b64_str= new String(compactsig_b64, "UTF-8"); + } catch (UnsupportedEncodingException ex) { + Logger.getLogger(CardDataParser.class.getName()).log(Level.SEVERE, null, ex); + } + return this; + } + + @Override + public String toString(){ + + String str="PubKeyData:"; + str+="\n\t data_size:"+data_size; + str+="\n\t data:"+toHexString(data); + str+="\n\t msg_size:"+msg_size; + str+="\n\t msg:"+toHexString(msg); + str+="\n\t sig_size:"+sig_size; + str+="\n\t signature:"+toHexString(signature); + str+="\n\t option_flags:"+option_flags+" "+Integer.toBinaryString(option_flags & 0xFFFF); + str+="\n\t pubkey_recid:"+pubkey_recid; + str+="\n\t pubkey:"+toHexString(pubkey); + str+="\n\t authentikey_recid:"+authentikey_recid; + str+="\n\t authentikey:"+toHexString(authentikey); + str+="\n\t authentikey_coordx:"+toHexString(authentikey_coordx); + str+="\n\t msg2_size:"+msg2_size; + str+="\n\t msg2:"+toHexString(msg2); + str+="\n\t sig2_size:"+sig2_size; + str+="\n\t signature2:"+toHexString(signature2); + str+="\n\t compactsig:"+toHexString(compactsig); + str+="\n\t compactsig_b64:"+toHexString(compactsig_b64); + str+="\n\t compactsig_b64_str:"+compactsig_b64_str; + str+="\n"; + return str; + } + + + /* convert a DER encoded signature to compact 65-byte format + input is hex string in DER format + output is hex string in compact 65-byteformat + http://bitcoin.stackexchange.com/questions/12554/why-the-signature-is-always-65-13232-bytes-long + https://bitcointalk.org/index.php?topic=215205.0 + */ + public static byte[] parseToCompactSig(byte[] sigin, int recid, boolean compressed) throws CardDataParserException { + + byte[] sigout= new byte[65]; + // parse input + byte first= sigin[0]; + if (first!= 0x30){ + throw new CardDataParserException("Wrong first byte!"); + } + byte lt= sigin[1]; + byte check= sigin[2]; + if (check!= 0x02){ + throw new CardDataParserException("Check byte should be 0x02"); + } + // extract r + byte lr= sigin[3]; + for (int i= 0; i<=31; i++){ + byte tmp= sigin[4+lr-1-i]; + if (lr>=(i+1)) { + sigout[32-i]= tmp; + } else{ + sigout[32-i]=0; + } + } + // extract s + check= sigin[4+lr]; + if (check!= 0x02){ + throw new CardDataParserException("Second check byte should be 0x02"); + } + byte ls= sigin[5+lr]; + if (lt != (lr+ls+4)){ + throw new CardDataParserException("Wrong lt value"); + } + for (int i= 0; i<=31; i++){ + byte tmp= sigin[5+lr+ls-i]; + if (ls>=(i+1)) { + sigout[64-i]= tmp; + } else{ + sigout[32-i]=0; + } + } + + // 1 byte header + if (recid>3 || recid<0){ + throw new CardDataParserException("Wrong recid value"); + } + if (compressed){ + sigout[0]= (byte)(27 + recid + 4 ); + }else{ + sigout[0]= (byte)(27 + recid); + } + + return sigout; + } + } + + public static class KeyList{ + + public ArrayList keys_nbr; + public ArrayList keys_type; + public ArrayList keys_partner; + public ArrayList keys_size; + public ArrayList keys_ACL; + public int key_nbr; + + public KeyList(byte[] response){ + + int capacity= response.length/10; + keys_nbr= new ArrayList<>(capacity); + keys_type= new ArrayList<>(capacity); + keys_partner= new ArrayList<>(capacity); + keys_size= new ArrayList<>(capacity); + keys_ACL= new ArrayList<>(capacity); + key_nbr=0; + + int base=0; + key_nbr=0; + while(base0){// datasize ==15// 16? + protocol_version_maj= response[base++]; + protocol_version_min= response[base++]; + applet_version_maj= response[base++]; + applet_version_min= response[base++]; + sec_mem_tot= ((response[base++]&0xff)<<8)+(response[base++]&0xff); + mem_tot= ((response[base++]&0xff)<<8)+(response[base++]&0xff); + sec_mem_free= ((response[base++]&0xff)<<8)+(response[base++]&0xff); + mem_free= ((response[base++]&0xff)<<8)+(response[base++]&0xff); + PINs_nbr= response[base++]; + keys_nbr= response[base++]; + logged_in= (short) (((response[base++]&0xff)<<8)+(response[base++]&0xff)); + } + } + + @Override + public String toString(){ + + String data= "CardStatus:"; + data+="\n\t Protocol major version: "+protocol_version_maj; + data+="\n\t Protocol minor version: "+protocol_version_min; + data+="\n\t Applet major version: "+applet_version_maj; + data+="\n\t Applet minor version: "+applet_version_min; + data+="\n\t Total secure memory: "+ sec_mem_tot; + data+="\n\t Total object memory: "+ mem_tot; + data+="\n\t Free secure memory: "+ sec_mem_free; + data+="\n\t Free object memory: "+ mem_free; + data+="\n\t Number of used PIN: "+ PINs_nbr; + data+="\n\t Number of used keys: "+ keys_nbr; + data+="\n\t Currently logged in identities: "+ logged_in + " " + Integer.toBinaryString(logged_in & 0xffff); + return data; + } + } + + /** + * Utility function that converts a byte array into an hexadecimal string. + * @param bytes + * @return String + */ + public static String toHexString(byte[] bytes) { + if (bytes==null) + return "null"; + return toHexString(bytes, 0, bytes.length, 0); + } + public static String toHexString(byte[] bytes, int off, int size, int blocksize) { + + if (bytes==null) + return "null"; + final String hexChars = "0123456789ABCDEF"; + StringBuffer sbTmp = new StringBuffer(); + char[] cTmp = new char[2]; + + for (int i = off; i < (off+size); i++) { + cTmp[0] = hexChars.charAt((bytes[i] & 0xF0) >>> 4); + cTmp[1] = hexChars.charAt(bytes[i] & 0x0F); + sbTmp.append(cTmp); + if (blocksize!=0 && ((i+1)%blocksize)==0) + sbTmp.append(' '); + } + + return sbTmp.toString(); + } + + public static class CardDataParserException extends Exception{ + CardDataParserException(String msg){ + super(msg); + } + } + +} diff --git a/src/main/java/org/satochip/satochipclient/SatoChipClient.java b/src/main/java/org/satochip/satochipclient/SatoChipClient.java index 6722c04..7b67d10 100644 --- a/src/main/java/org/satochip/satochipclient/SatoChipClient.java +++ b/src/main/java/org/satochip/satochipclient/SatoChipClient.java @@ -18,1161 +18,17 @@ package org.satochip.satochipclient; -import com.google.bitcoin.core.ECKey; -import com.google.bitcoin.core.ECKey.ECDSASignature;//import java.io.OutputStream; -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.BITCOIN_SIGNED_MESSAGE_HEADER_BYTES; -import static com.google.bitcoin.core.Utils.uint32ToByteStreamLE; -import com.google.bitcoin.core.VarInt; -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.io.UnsupportedEncodingException; -import static java.lang.System.exit; -import java.math.BigInteger; -import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.smartcardio.CardException; -import javax.xml.bind.DatatypeConverter; -import static org.junit.Assert.assertArrayEquals; // used to convert hexstring to byte[] -import org.spongycastle.asn1.ASN1InputStream; -import org.spongycastle.asn1.ASN1Integer; -import org.spongycastle.asn1.DLSequence; -import org.spongycastle.crypto.digests.SHA256Digest; -import org.spongycastle.util.encoders.Base64; - - public class SatoChipClient { - - - /* constants declaration */ - - // authentikey - public static byte[] authentikey; - public static DeterministicKey masterkey; - public static final byte[] DEFAULT_ACL={0x00,0x01, 0x00,0x01, 0x00,0x01}; - - /** - * Utility function that converts a byte array into an hexadecimal string. - * @param bytes - */ - public static String toString(byte[] bytes) { - - final String hexChars = "0123456789ABCDEF"; - StringBuffer sbTmp = new StringBuffer(); - char[] cTmp = new char[2]; - - //System.out.println(bytes);//debug - for (int i = 0; i < bytes.length; i++) { - cTmp[0] = hexChars.charAt((bytes[i] & 0xF0) >>> 4); - cTmp[1] = hexChars.charAt(bytes[i] & 0x0F); - sbTmp.append(cTmp); - } - //System.out.println(sbTmp.toString());//debug - - return sbTmp.toString(); - } - - /* convert a DER encoded signature to compact 65-byte format - input is hex string in DER format - output is hex string in compact 65-byteformat - http://bitcoin.stackexchange.com/questions/12554/why-the-signature-is-always-65-13232-bytes-long - https://bitcointalk.org/index.php?topic=215205.0 - */ - public static byte[] toCompactSig(byte[] sigin, int recid, boolean compressed) { - - byte[] sigout= new byte[65]; - // parse input - byte first= sigin[0]; - if (first!= 0x30){ - System.out.println("Wrong first byte!"); - return new byte[0]; - } - byte lt= sigin[1]; - byte check= sigin[2]; - if (check!= 0x02){ - System.out.println("Check byte should be 0x02"); - return new byte[0]; - } - // extract r - byte lr= sigin[3]; - for (int i= 0; i<=31; i++){ - byte tmp= sigin[4+lr-1-i]; - if (lr>=(i+1)) { - sigout[32-i]= tmp; - } else{ - sigout[32-i]=0; - } - } - // extract s - check= sigin[4+lr]; - if (check!= 0x02){ - System.out.println("Second check byte should be 0x02"); - return new byte[0]; - } - byte ls= sigin[5+lr]; - if (lt != (lr+ls+4)){ - System.out.println("Wrong lt value"); - return new byte[0]; - } - for (int i= 0; i<=31; i++){ - byte tmp= sigin[5+lr+ls-i]; - if (ls>=(i+1)) { - sigout[64-i]= tmp; - } else{ - sigout[32-i]=0; - } - } - - // 1 byte header - if (recid>3 || recid<0){ - System.out.println("Wrong recid value"); - return new byte[0]; - } - if (compressed){ - sigout[0]= (byte)(27 + recid + 4 ); - }else{ - sigout[0]= (byte)(27 + recid); - } - -// // change endianness (debug) -// byte[] tmpr= new byte[32]; -// byte[] tmps= new byte[32]; -// for (int i= 0; i<=31; i++){ -// tmpr[i]= sigout[1+i]; -// tmps[i]= sigout[33+i]; -// } -// for (int i= 0; i<=31; i++){ -// sigout[1+i]=tmpr[31-i]; -// sigout[33+i]=tmps[31-i]; -// } - - return sigout; - } - - /* convert a DER encoded signature to Bitcoinj ECDSASignature format - input is byte[] in DER format - output is ECDSASignature - */ - public static ECDSASignature toECDSASignature(byte[] sigin) { - - // unfortunately, the following returns exception: - // java.lang.ClassCastException: org.spongycastle.asn1.ASN1Integer cannot be cast to org.spongycastle.asn1.DERInteger - // at com.google.bitcoin.core.ECKey$ECDSASignature.decodeFromDER(ECKey.java:386) - //return ECKey.ECDSASignature.decodeFromDER(sigin); - - try { - ASN1InputStream decoder = new ASN1InputStream(sigin); - DLSequence seq = (DLSequence) decoder.readObject(); - ASN1Integer r, s; - try { - r = (ASN1Integer) seq.getObjectAt(0); - s = (ASN1Integer) seq.getObjectAt(1); - } catch (ClassCastException e) { - throw new IllegalArgumentException(e); - } - decoder.close(); - // OpenSSL deviates from the DER spec by interpreting these values as unsigned, though they should not be - // Thus, we always use the positive versions. See: http://r6.ca/blog/20111119T211504Z.html - return new ECDSASignature(r.getPositiveValue(), s.getPositiveValue()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - // sign message formating - public static byte[] hashMagicMessage(byte[] message){ - - byte[] tohash; - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - bos.write(BITCOIN_SIGNED_MESSAGE_HEADER_BYTES.length); - bos.write(BITCOIN_SIGNED_MESSAGE_HEADER_BYTES); - VarInt size = new VarInt(message.length); - bos.write(size.encode()); - bos.write(message); - tohash= bos.toByteArray(); - } catch (IOException e) { - throw new RuntimeException(e); // Cannot happen. - } - - System.out.println("Message to hash: " + toString(tohash)); - - Sha256Hash hash = Sha256Hash.create(tohash); - return hash.getBytes(); - } - - public static void TestObject(CardConnector cc) throws CardConnectorException{ - byte[] objACL= DEFAULT_ACL;//{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - int objId=123456; - - System.out.println("TestObject:"); - 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); - cc.cardDeleteObject(objId, (byte)0x01); - } - } - - public static void TestGenerateKeyPair(CardConnector cc, 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("ERROR: algorithm not supported!"); - return; - } - - System.out.println("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); - - // list key - cc.cardGetStatus(); - System.out.println("*****ListKey*****"); - cc.cardListKeys(); - System.out.println("*****************\n\n"); - } - - public static void TestGenerateSymmetricKey(CardConnector cc, byte algtype, byte keynbr, short keysize) throws CardConnectorException{ - - 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); - - // list key - cc.cardGetStatus(); - System.out.println("*****ListKey*****"); - cc.cardListKeys(); - System.out.println("*****************\n\n"); - } - - public static void TestImportKey(CardConnector cc, byte key_type, byte key_nbr, short key_size) throws CardConnectorException{ - - 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"); - - } - - public static void TestComputeSign(CardConnector cc, byte CM, byte key_sign, byte key_verif) throws CardConnectorException{ - -// byte[] buffer= {'H','e','l','l','o',' ','w','o','r','l','d'}; -// byte[] buffer= {'H','e','l','l','o',' ','w','o','r','l','x'}; -// byte[] buffer= {'H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d','H','e','l','l','o',' ','w','o','r','l','d'}; - byte[] buffer= {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'}; -// byte[] buffer= {'t','e','s','t'}; - 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("Test ComputeSign(CM="+stralg+", keynb="+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:" + toString(signature)); - - // computeverify - System.out.println("Test ComputeVerify(CM="+stralg+", keynb="+key_verif+")"); - byte[] response= cc.cardComputeSign(key_verif, CM, JCconstants.MODE_VERIFY, buffer, signature); - - System.out.println("Verify signature with wrong data:"); - response= cc.cardComputeSign(key_verif, CM, JCconstants.MODE_VERIFY, buffer_wrong, signature); - System.out.println("\n\n\n"); - - } - - public static void TestComputeCrypt(CardConnector cc, byte CM, byte keynbr, byte keynbrdecrypt) throws CardConnectorException{ - - 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("msg:"+toString(msg)); - - System.out.println("TestComputeCrypt(CM="+stralg+", keynb="+keynbr+")"); - msgcrypt= cc.cardComputeCrypt(keynbr, CM, JCconstants.MODE_ENCRYPT, msg); - //System.out.println("msgcrypt:"+toString(msgcrypt)); - - System.out.println("TestComputeDecrypt(CM="+stralg+", keynb="+keynbrdecrypt+")"); - msgdecrypt= cc.cardComputeCrypt(keynbrdecrypt, CM, JCconstants.MODE_DECRYPT, msgcrypt); - //System.out.println("msgdecrypt:"+toString(msgdecrypt)); - - assertArrayEquals(msg,msgdecrypt); - - strmsg+=strmsg; - } - - } - - public static void TestSHA512(CardConnector cc) throws CardConnectorException{ - - System.out.println("Test SHA512"); - System.out.println("*********** Hashing Test ******************"); - byte[] msg1= new byte[0]; - String hash1= "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"; - byte[] response= cc.cardComputeSha512(msg1); - System.out.println("hash expected:"+ hash1); - System.out.println("hash computed:"+ toString(response)); - - byte[] msg2= {'a','b','c'}; - String hash2= "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"; - response= cc.cardComputeSha512(msg2); - System.out.println("hash expected:"+ hash2); - System.out.println("hash computed:"+ toString(response)); - - String key1="0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"; - String data1="4869205468657265"; - hash1= "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"; - byte[] bkey1= DatatypeConverter.parseHexBinary(key1); - byte[] bdata1= DatatypeConverter.parseHexBinary(data1); - response= cc.cardComputeHmacSha512(bkey1, bdata1); - System.out.println("hash expected:"+ hash1); - System.out.println("hash computed:"+ toString(response)); - - String key2="4a656665"; - String data2="7768617420646f2079612077616e7420666f72206e6f7468696e673f"; - hash2="164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737"; - byte[] bkey2= DatatypeConverter.parseHexBinary(key2); - byte[] bdata2= DatatypeConverter.parseHexBinary(data2); - response= cc.cardComputeHmacSha512(bkey2, bdata2); - System.out.println("hash expected:"+ hash2); - System.out.println("hash computed:"+ toString(response)); - System.out.println("*******************************************"); - System.out.println("\n\n\n"); - - } - - public static byte[] TestBip32ImportSeed(CardConnector cc, String strseed) throws CardConnectorException{ - - // 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); - //String masterkey, chaincode; - long stopTime = System.currentTimeMillis(); - long elapsedTime = stopTime - startTime; - System.out.println("Test Bip32ImportSeed"); - System.out.println("elapsed time: "+elapsedTime); - - // recover pubkey+sig - // this is a key derived from the seed and can be used to authenticate data from wallet - int coordx_size = ((int)(response[0] & 0xff)<<8) + ((int)(response[1] & 0xff)); - byte[] msg= new byte[2+coordx_size]; - System.arraycopy(response, 0, msg, 0, coordx_size+2); - byte[] coordx= new byte[coordx_size]; - System.arraycopy(response, 2, coordx, 0, coordx_size); - int sig_size = ((int)(response[coordx_size+2] & 0xff)<<8) + ((int)(response[coordx_size+3] & 0xff)); - byte[] signature= new byte[sig_size]; - int nb_deleted = ((int)(response[2+coordx_size+2+sig_size] & 0xff)<<8) + ((int)(response[2+coordx_size+2+sig_size+1] & 0xff)); - System.arraycopy(response, coordx_size+4, signature, 0, sig_size); - ECKey.ECDSASignature ecdsasig= toECDSASignature(signature); - Sha256Hash msghash= Sha256Hash.create(msg); // compute sha256 of message -// System.out.println("Public key coordx size:"+ coordx_size); -// System.out.println("Public key coordx computed:"+ toString(coordx)); -// System.out.println("Public key signature size:"+ sig_size); -// System.out.println("Public key signature computed:"+ toString(signature)); - System.out.println("Number of Bip32 objects deleted:"+ nb_deleted); -// System.out.println("****** compact signature ******"); - int recid=-1; - ECKey pkey=null; - for (int i=0; i<4; i++){ - pkey= ECKey.recoverFromSignature(i, ecdsasig, msghash, true); - if (pkey!=null){ - byte[] coordxkey= new byte[coordx_size]; - System.arraycopy(pkey.getPubKey(), 1, coordxkey, 0, coordx_size); - if (Arrays.equals(coordx,coordxkey)){ - recid=i; - authentikey= pkey.getPubKey(); - System.out.println("#recid: "+i+" AuthentiKey:" + toString(authentikey)); - break; - } - } - } - - return pkey.getPubKey(); - } - - public static DeterministicKey TestBip32ImportSeed(String strseed){ - - // create SW masterkey with bitcoinj - byte[] seed= DatatypeConverter.parseHexBinary(strseed); - masterkey= HDKeyDerivation.createMasterPrivateKey(seed); - - return masterkey; - } - - public static byte[] TestBip32GetExtendedKey(CardConnector cc, byte[] bip32path, byte debug) throws CardConnectorException{ - - long startTime = System.currentTimeMillis(); - byte[] response= cc.cardBip32GetExtendedKey(bip32path); - //byte debug=0x01; - //byte[] response = cc.exchangeAPDU(JCconstants.CardEdge_CLA, JCconstants.INS_BIP32_GET_EXTENDED_KEY, (byte)(bip32path.length/4), debug, bip32path, (byte)0x00); - - long stopTime = System.currentTimeMillis(); - long elapsedTime = stopTime - startTime; - System.out.println("elapsed time: "+elapsedTime); - System.out.println("Extended key for "+toString(bip32path)); - - int coordx_size = ((int)(response[0] & 0xff)<<8) + ((int)(response[1] & 0xff)); - byte[] msg= new byte[2+coordx_size]; - System.arraycopy(response, 0, msg, 0, coordx_size+2); - byte[] coordx= new byte[coordx_size]; - System.arraycopy(response, 2, coordx, 0, coordx_size); - int sig_size = ((int)(response[2+coordx_size] & 0xff)<<8) + ((int)(response[2+coordx_size+1] & 0xff)); - byte[] signature= new byte[sig_size]; - System.arraycopy(response, coordx_size+4, signature, 0, sig_size); - ECKey.ECDSASignature ecdsasig= toECDSASignature(signature); - Sha256Hash msghash= Sha256Hash.create(msg); // compute sha256 of message - int recid=-1; - ECKey pkey=null; - for (int i=0; i<4; i++){ - pkey= ECKey.recoverFromSignature(i, ecdsasig, msghash, true); - if (pkey!=null){ - byte[] coordxkey= new byte[coordx_size]; - System.arraycopy(pkey.getPubKey(), 1, coordxkey, 0, coordx_size); - if (Arrays.equals(coordx,coordxkey)){ - recid=i; - System.out.println("#recid: "+i+" PubKey:" + toString(pkey.getPubKey())); - break; - } - } - } - if (recid == -1) - throw new CardConnectorException("Unable to recover public key from signature"); - - // authentikey signature - if (authentikey==null) - authentikey= TestBip32GetAuthentiKey(cc); - - byte[] coordx2= new byte[2+coordx_size+2+sig_size]; - System.arraycopy(response, 0, coordx2, 0, 2+coordx_size+2+sig_size); - int sig_size2 = ((int)(response[2+coordx_size+2+sig_size] & 0xff)<<8) + ((int)(response[2+coordx_size+2+sig_size+1] & 0xff)); - byte[] signature2= new byte[sig_size2]; - System.arraycopy(response, 2+coordx_size+2+sig_size+2, signature2, 0, sig_size2); - ecdsasig= toECDSASignature(signature2); - msghash= Sha256Hash.create(coordx2);// compute sha256 of message - recid=-1; - ECKey akey=null; - for (int i=0; i<4; i++){ - akey= ECKey.recoverFromSignature(i, ecdsasig, msghash, true); - if (akey!=null && Arrays.equals(akey.getPubKey(), authentikey)){ - recid=i; - System.out.println("#recid: " + recid+ " Authentikey:" + toString(akey.getPubKey())); - break; - } - } - if (recid == -1) - throw new CardConnectorException("Unable to recover authentikey from signature"); - - return pkey.getPubKey(); - - } - - public static byte[] TestGetPublicKeyFromPrivate(CardConnector cc, byte keynbr) throws CardConnectorException{ - - System.out.println("TestGetPublicKeyFromPrivate - keynbr:"+keynbr); - byte[] response= cc.cardGetPublicKeyFromPrivate(keynbr); - - int coordx_size = ((int)(response[0] & 0xff)<<8) + ((int)(response[1] & 0xff)); - byte[] msg= new byte[2+coordx_size]; - System.arraycopy(response, 0, msg, 0, coordx_size+2); - byte[] coordx= new byte[coordx_size]; - System.arraycopy(response, 2, coordx, 0, coordx_size); - int sig_size = ((int)(response[2+coordx_size] & 0xff)<<8) + ((int)(response[2+coordx_size+1] & 0xff)); - byte[] signature= new byte[sig_size]; - System.arraycopy(response, coordx_size+4, signature, 0, sig_size); - ECKey.ECDSASignature ecdsasig= toECDSASignature(signature); - Sha256Hash msghash= Sha256Hash.create(msg); // compute sha256 of message - int recid=-1; - ECKey pkey=null; - for (int i=0; i<4; i++){ - pkey= ECKey.recoverFromSignature(i, ecdsasig, msghash, true); - if (pkey!=null){ - byte[] coordxkey= new byte[coordx_size]; - System.arraycopy(pkey.getPubKey(), 1, coordxkey, 0, coordx_size); - if (Arrays.equals(coordx,coordxkey)){ - recid=i; - System.out.println("#recid: "+i+" PubKey:" + toString(pkey.getPubKey())); - break; - } - } - } - if (recid == -1) - throw new CardConnectorException("Unable to recover public key from signature"); - - return pkey.getPubKey(); - - } - - public static byte[] TestBip32GetExtendedKey(byte[] bip32path){ - - // create SW extendedkey with bitcoinj - int bip32depth= bip32path.length/4; - DeterministicKey parent= masterkey; // imported from seed - DeterministicKey child= null; - for (int i=0; i32){ - // recover authentikey from card - byte[] akey= TestBip32GetAuthentiKey(cc); - - // recover key from sig - int sig_size = ((int)(response[32] & 0xff)<<8) + ((int)(response[33] & 0xff)); - byte[] signature= new byte[sig_size]; - System.arraycopy(response, 34, signature, 0, sig_size); - ECKey.ECDSASignature ecdsasig= toECDSASignature(signature); - Sha256Hash msghash= Sha256Hash.create(txhash); // compute sha256 of message - //System.out.println("Public key signature size:"+ sig_size); - //System.out.println("Public key signature computed:"+ toString(signature)); - for (int recid=0; recid<4; recid++){ - ECKey pkey= ECKey.recoverFromSignature(recid, ecdsasig, msghash, true); - if (pkey!=null && Arrays.equals(akey, pkey.getPubKey())) - System.out.println("#recid: "+recid+"Public key:" + pkey.toString()); - } - - } - } - - // 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 outputs = (ArrayList) 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(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(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 inputs = (ArrayList) 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(); -// 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. - } - } - /** * @param args * @throws CardException * @throws NoSuchAlgorithmException */ - public static void main(String[] args) throws CardException, NoSuchAlgorithmException, UnsupportedEncodingException { - - try { - // applet aid - byte[] byteAID= {0x53,0x61,0x74,0x6f,0x43,0x68,0x69,0x70}; //SatoChip - //byte[] byteAID= {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x00}; //cardedge jcdk-222 - //byte[] byteAID= {(byte)0xFF ,0x42 ,0x54 ,0x43 ,0x48 ,0x49 ,0x50}; //BTChip - - // CardConnector object - CardConnector cc= new CardConnector(); - - //select applet - byte[] response={}; - System.out.println("cardselect"); - cc.cardSelect(byteAID); - - //get status - try{ - cc.cardGetStatus(); - }catch(CardConnectorException ex){ - System.out.println("CardConnectorException: "+ex.getMessage()+" "+Integer.toHexString(ex.getIns() & 0xff)+" "+Integer.toHexString(ex.getSW12() & 0xffff)); - } - //exit(0); - - // setup - byte pin_tries_0= 0x10; - byte ublk_tries_0= (byte) 0x10; - byte[] pin_0={0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38}; - byte[] ublk_0={0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38}; - byte pin_tries_1=0x10; - byte ublk_tries_1=(byte) 0x10; - byte[] pin_1={0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38}; - byte[] ublk_1={0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38}; - short secmemsize= 0x1000; - short memsize= 0x1000; - byte create_object_ACL= 0x01; - byte create_key_ACL= 0x01; - byte create_pin_ACL= 0x01; - try{ - 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); - }catch(CardConnectorException ex){ - System.out.println("CardConnectorException: "+ex.getMessage()+" "+Integer.toHexString(ex.getIns() & 0xff)+" "+Integer.toHexString(ex.getSW12() & 0xffff)); - } - //get status - cc.cardGetStatus(); - //exit(0); - - // verifPIN - byte pin_nbr= 0x00; - cc.cardVerifyPIN(pin_nbr, pin_1); - // list key - cc.cardGetStatus(); - System.out.println("*****ListKey*****"); - cc.cardListKeys(); - System.out.println("*****************"); - - // test object creation/destruction - TestObject(cc); - //exit(0); - - // gen key - //TestGenerateKeyPair(cc, JCconstants.ALG_RSA_CRT, (byte)0x00, (byte)0x01, (short)512);// doesn't work? - TestGenerateKeyPair(cc, JCconstants.ALG_RSA, (byte)0x02, (byte)0x03, (short)512); - //TestGenerateKeyPair(cc, JCconstants.ALG_RSA, (byte)0x04, (byte)0x05, (short)1024); - TestGenerateKeyPair(cc, JCconstants.ALG_EC_FP, (byte)0x06, (byte)0xff, (short)256); - TestGetPublicKeyFromPrivate(cc, (byte)0x06); - TestGenerateSymmetricKey(cc, JCconstants.TYPE_DES, (byte)0x0A, (short)64); - TestGenerateSymmetricKey(cc, JCconstants.TYPE_DES, (byte)0x0B, (short)128); - TestGenerateSymmetricKey(cc, JCconstants.TYPE_DES, (byte)0x0C, (short)192); - TestGenerateSymmetricKey(cc, JCconstants.TYPE_AES, (byte)0x0D, (short)128); - TestGenerateSymmetricKey(cc, JCconstants.TYPE_AES, (byte)0x0E, (short)192); - TestGenerateSymmetricKey(cc, JCconstants.TYPE_AES, (byte)0x0F, (short)256); - //exit(0); - - // test computeCrypt - //TestComputeCrypt(cc, JCconstants.ALG_RSA_PKCS1, (byte)0x01, (byte)0x00); // - //TestComputeCrypt(cc, JCconstants.ALG_RSA_NOPAD, (byte)0x01, (byte)0x00); // to do: padding - TestComputeCrypt(cc, JCconstants.ALG_DES_CBC_NOPAD, (byte)0x0A, (byte)0x0A); - TestComputeCrypt(cc, JCconstants.ALG_DES_ECB_NOPAD, (byte)0x0B, (byte)0x0B); - TestComputeCrypt(cc, JCconstants.ALG_DES_CBC_NOPAD, (byte)0x0C, (byte)0x0C); - TestComputeCrypt(cc, JCconstants.ALG_AES_BLOCK_128_CBC_NOPAD, (byte)0x0D, (byte)0x0D); - TestComputeCrypt(cc, JCconstants.ALG_AES_BLOCK_128_ECB_NOPAD, (byte)0x0E, (byte)0x0E); - TestComputeCrypt(cc, JCconstants.ALG_AES_BLOCK_128_CBC_NOPAD, (byte)0x0F, (byte)0x0F); - //exit(0); - - // import private key - TestImportKey(cc, JCconstants.TYPE_RSA_CRT_PRIVATE, (byte)0x00, (short)512); - TestImportKey(cc, JCconstants.TYPE_RSA_PUBLIC, (byte)0x01, (short)512); - TestImportKey(cc, JCconstants.TYPE_RSA_PRIVATE, (byte)0x02, (short)512); - TestImportKey(cc, JCconstants.TYPE_RSA_PUBLIC, (byte)0x03, (short)512); - TestImportKey(cc, JCconstants.TYPE_EC_FP_PRIVATE, (byte)0x06, (short)256); - TestGetPublicKeyFromPrivate(cc, (byte)0x06); - //TestImportKey(cc, JCconstants.TYPE_EC_FP_PUBLIC, (byte)0x07, (short)256); // doesn't work? - TestImportKey(cc, JCconstants.TYPE_DES, (byte)0x0A, (short)64); - TestImportKey(cc, JCconstants.TYPE_DES, (byte)0x0B, (short)128); - TestImportKey(cc, JCconstants.TYPE_DES, (byte)0x0C, (short)192); - TestImportKey(cc, JCconstants.TYPE_AES, (byte)0x0D, (short)128); - TestImportKey(cc, JCconstants.TYPE_AES, (byte)0x0E, (short)192); - TestImportKey(cc, JCconstants.TYPE_AES, (byte)0x0F, (short)256); - - //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(cc, JCconstants.ALG_DES_CBC_NOPAD, (byte)0x0A, (byte)0x0A); - TestComputeCrypt(cc, JCconstants.ALG_DES_ECB_NOPAD, (byte)0x0B, (byte)0x0B); - TestComputeCrypt(cc, JCconstants.ALG_DES_CBC_NOPAD, (byte)0x0C, (byte)0x0C); - TestComputeCrypt(cc, JCconstants.ALG_AES_BLOCK_128_CBC_NOPAD, (byte)0x0D, (byte)0x0D); - TestComputeCrypt(cc, JCconstants.ALG_AES_BLOCK_128_ECB_NOPAD, (byte)0x0E, (byte)0x0E); - TestComputeCrypt(cc, JCconstants.ALG_AES_BLOCK_128_CBC_NOPAD, (byte)0x0F, (byte)0x0F); - //exit(0) - - // computesign - //TestComputeSign(byte CM, byte key_sign, byte key_verif); - //TestComputeSign(JCconstants.ALG_RSA_PKCS1, (byte) 0, (byte) 1); - //TestComputeSign(JCconstants.ALG_ECDSA_SHA256, (byte) 4, (byte) 5); - //TestComputeSign(CM_ECDSA_SHA, (byte) 4, (byte) 5); - // testing Sha512 & HmacSha512 - //TestSHA512(); - - // import seed - String strseed= "000102030405060708090a0b0c0d0e0f"; - TestBip32ImportSeed(cc, strseed); - TestBip32ImportSeed(strseed); - for (int val=0; val<1; val++){ - for (int depth=1; depth<2; depth++){ - byte[] bip32path= new byte[4*depth]; - for (int i=0; ilength || encodedSig[offset]!=(byte)0x02 || (encodedSig[offset+1]&0x80)!=0) + return false; + int rLength = (int)encodedSig[offset+1]&0x7f; + if (offset+rLength+2 > length) + return false; + if (encodedSig[offset+2]==0x00 && (encodedSig[offset+3]&0x80)==0) + return false; + offset += rLength + 2; + // + // Check S + // + if (offset+2>length || encodedSig[offset]!=(byte)0x02 || (encodedSig[offset+1]&0x80)!=0) + return false; + int sLength = (int)encodedSig[offset+1]&0x7f; + if (offset+sLength+2 > length) + return false; + if (encodedSig[offset+2]==0x00 && (encodedSig[offset+3]&0x80)==0) + return false; + offset += sLength + 2; + // + // There must be a single byte appended to the signature + // + return (offset == encodedSig.length-1); + } + + /** + * Returns the key creation time + * + * @return Key creation time (seconds) + */ + public long getCreationTime() { + return creationTime; + } + + /** + * Sets the key creation time + * + * @param creationTime Key creation time (seconds) + */ + public void setCreationTime(long creationTime) { + this.creationTime = creationTime; + } + + /** + * Returns the key label + * + * @return Key label + */ + public String getLabel() { + return label; + } + + /** + * Sets the key label + * + * @param label Key label + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * Checks if this is a change key + * + * @return TRUE if this is a change key + */ + public boolean isChange() { + return isChange; + } + + /** + * Sets change key status + * + * @param isChange TRUE if this is a change key + */ + public void setChange(boolean isChange) { + this.isChange = isChange; + } + + /** + * Returns the public key (as used in transaction scriptSigs). A compressed + * public key is 33 bytes and starts with '02' or '03' while an uncompressed + * public key is 65 bytes and starts with '04'. + * + * @return Public key + */ + public byte[] getPubKey() { + return pubKey; + } + + /** + * Returns the public key hash as used in addresses. The hash is 20 bytes. + * + * @return Public key hash + */ + public byte[] getPubKeyHash() { + if (pubKeyHash == null) + pubKeyHash = Utils.sha256Hash160(pubKey); + return pubKeyHash; + } + + /** + * Checks if the public key is compressed + * + * @return TRUE if the public key is compressed + */ + public boolean isCompressed() { + return isCompressed; + } + + /** + * Verifies a signature for the signed contents using the public key + * + * @param contents The signed contents + * @param signature DER-encoded signature + * @return TRUE if the signature if valid, FALSE otherwise + * @throws ECException Unable to verify the signature + */ + public boolean verifySignature(byte[] contents, byte[] signature) throws ECException { + boolean isValid = false; + // + // Decode the DER-encoded signature and get the R and S values + // + ECDSASignature sig = new ECDSASignature(signature); + // + // Get the double SHA-256 hash of the signed contents + // + // A null contents will result in a hash with the first byte set to 1 and + // all other bytes set to 0. This is needed to handle a bug in the reference + // client where it doesn't check for an error when serializing a transaction + // and instead uses the error code as the hash. + // + byte[] contentsHash; + if (contents != null) { + contentsHash = Utils.doubleDigest(contents); + } else { + contentsHash = new byte[32]; + contentsHash[0] = 0x01; + } + // + // Verify the signature + // + try { + ECDSASigner signer = new ECDSASigner(); + ECPublicKeyParameters params = new ECPublicKeyParameters( + ecParams.getCurve().decodePoint(pubKey), ecParams); + signer.init(false, params); + isValid = signer.verifySignature(contentsHash, sig.getR(), sig.getS()); + } catch (RuntimeException exc) { + throw new ECException("Exception while verifying signature: "+exc.getMessage()); + } + return isValid; + } + + /** + *

Given the components of a signature and a selector value, recover and return the public key + * that generated the signature according to the algorithm in SEC1v2 section 4.1.6.

+ * + *

The recID is an index from 0 to 3 which indicates which of the 4 possible keys is the correct one. + * Because the key recovery operation yields multiple potential keys, the correct key must either be + * stored alongside the signature, or you must be willing to try each recId in turn until you find one + * that outputs the key you are expecting.

+ * + *

If this method returns null, it means recovery was not possible and recID should be iterated.

+ * + *

Given the above two points, a correct usage of this method is inside a for loop from 0 to 3, and if the + * output is null OR a key that is not the one you expect, you try again with the next recID.

+ * + * @param recID Which possible key to recover. + * @param sig R and S components of the signature + * @param e The double SHA-256 hash of the original message + * @param compressed Whether or not the original public key was compressed + * @return An ECKey containing only the public part, or null if recovery wasn't possible + */ + protected static ECKey recoverFromSignature(int recID, ECDSASignature sig, BigInteger e, boolean compressed) { + BigInteger n = ecParams.getN(); + BigInteger i = BigInteger.valueOf((long)recID / 2); + BigInteger x = sig.getR().add(i.multiply(n)); + // + // Convert the integer x to an octet string X of length mlen using the conversion routine + // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉. + // Convert the octet string (16 set binary digits)||X to an elliptic curve point R using the + // conversion routine specified in Section 2.3.4. If this conversion routine outputs 'invalid', then + // do another iteration. + // + // More concisely, what these points mean is to use X as a compressed public key. + // + SecP256K1Curve curve = (SecP256K1Curve)ecParams.getCurve(); + BigInteger prime = curve.getQ(); + if (x.compareTo(prime) >= 0) { + return null; + } + // + // Compressed keys require you to know an extra bit of data about the y-coordinate as + // there are two possibilities. So it's encoded in the recID. + // + ECPoint R = decompressKey(x, (recID & 1) == 1); + if (!R.multiply(n).isInfinity()) + return null; + // + // For k from 1 to 2 do the following. (loop is outside this function via iterating recId) + // Compute a candidate public key as: + // Q = mi(r) * (sR - eG) + // + // Where mi(x) is the modular multiplicative inverse. We transform this into the following: + // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) + // Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). + // In the above equation, ** is point multiplication and + is point addition (the EC group operator). + // + // We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive + // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8. + // + BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); + BigInteger rInv = sig.getR().modInverse(n); + BigInteger srInv = rInv.multiply(sig.getS()).mod(n); + BigInteger eInvrInv = rInv.multiply(eInv).mod(n); + ECPoint q = ECAlgorithms.sumOfTwoMultiplies(ecParams.getG(), eInvrInv, R, srInv); + return new ECKey(q.getEncoded(compressed)); + } + + public static byte[] recoverFromSignature(int recID, byte[] msg, byte[] sig, boolean doublehash) throws ECException{ + + //return CardConnector.recoverPublicKeyFromSig(recID, msg, sig, doublehash); + + byte[] digest= new byte[32]; + SHA256Digest sha256= new SHA256Digest(); + sha256.reset(); + sha256.update(msg, 0, msg.length); + sha256.doFinal(digest, 0); + if (doublehash){ + sha256.reset(); + sha256.update(digest, 0, digest.length); + sha256.doFinal(digest, 0); + } + BigInteger bi= new BigInteger(1,digest); + ECDSASignature ecdsaSig= new ECDSASignature(sig); + ECKey k= ECKey.recoverFromSignature(recID, ecdsaSig, bi, true); + + if (k!=null) + return k.getPubKey(); + else + return null; + + } + + public static byte[] recoverFromSignature(byte[] coordx, byte[] msg, byte[] sig, boolean doublehash) throws ECException{ + byte[] pubkey= null; + int recID =-1; + for (int i=0; i<4; i++){ + pubkey= recoverFromSignature(i, msg, sig, doublehash); + if (pubkey!=null){ + byte[] coordxkey= Arrays.copyOfRange(pubkey, 1, 1+coordx.length); + if (Arrays.equals(coordx,coordxkey)){ + recID=i; + return pubkey; + } + } + } + if (recID == -1) + throw new ECException("Unable to recover public key from signature"); + + return pubkey; + } + public static int recidFromSignature(byte[] coordx, byte[] msg, byte[] sig, boolean doublehash) throws ECException{ + + byte[] pubkey= null; + int recID =-1; + for (int i=0; i<4; i++){ + pubkey= recoverFromSignature(i, msg, sig, doublehash); + if (pubkey!=null){ + byte[] coordxkey= Arrays.copyOfRange(pubkey, 1, 1+coordx.length); + if (Arrays.equals(coordx,coordxkey)){ + recID=i; + return recID; + } + } + } + if (recID == -1) + throw new ECException("Unable to recover public key from signature"); + + return recID; + } + + + /** + * Decompress a compressed public key (x coordinate and low-bit of y-coordinate). + * + * @param xBN X-coordinate + * @param yBit Sign of Y-coordinate + * @return Uncompressed public key + */ + private static ECPoint decompressKey(BigInteger xBN, boolean yBit) { + SecP256K1Curve curve = (SecP256K1Curve)ecParams.getCurve(); + ECFieldElement x = curve.fromBigInteger(xBN); + ECFieldElement alpha = x.multiply(x.square().add(curve.getA())).add(curve.getB()); + ECFieldElement beta = alpha.sqrt(); + if (beta == null) + throw new IllegalArgumentException("Invalid point compression"); + ECPoint ecPoint; + BigInteger nBeta = beta.toBigInteger(); + if (nBeta.testBit(0) == yBit) { + ecPoint = curve.createPoint(x.toBigInteger(), nBeta); + } else { + ECFieldElement y = curve.fromBigInteger(curve.getQ().subtract(nBeta)); + ecPoint = curve.createPoint(x.toBigInteger(), y.toBigInteger()); + } + return ecPoint; + } + + /** + * Checks if two objects are equal + * + * @param obj The object to check + * @return TRUE if the object is equal + */ + @Override + public boolean equals(Object obj) { + return (obj!=null && (obj instanceof ECKey) && Arrays.equals(pubKey, ((ECKey)obj).pubKey)); + } + + /** + * Returns the hash code for this object + * + * @return Hash code + */ + @Override + public int hashCode() { + return Arrays.hashCode(pubKey); + } +} diff --git a/src/main/java/org/toporin/bitcoincore/SignatureException.java b/src/main/java/org/toporin/bitcoincore/SignatureException.java new file mode 100644 index 0000000..fdf372d --- /dev/null +++ b/src/main/java/org/toporin/bitcoincore/SignatureException.java @@ -0,0 +1,41 @@ +/** + * Copyright 2013-2014 Ronald W Hoffman + * + * 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.toporin.bitcoincore; + +/** + * A SignatureException is thrown if a message signature is not valid + */ +public class SignatureException extends Exception { + + /** + * Creates a new exception with a detail message + * + * @param msg Detail message + */ + public SignatureException(String msg) { + super(msg); + } + + /** + * Creates a new exception with a detail message and cause + * + * @param msg Detail message + * @param t Caught exception + */ + public SignatureException(String msg, Throwable t) { + super(msg, t); + } +} diff --git a/src/main/java/org/toporin/bitcoincore/Utils.java b/src/main/java/org/toporin/bitcoincore/Utils.java new file mode 100644 index 0000000..8a3d4ad --- /dev/null +++ b/src/main/java/org/toporin/bitcoincore/Utils.java @@ -0,0 +1,571 @@ +/** + * Copyright 2011 Google Inc. + * Copyright 2013-2014 Ronald W Hoffman + * + * 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.toporin.bitcoincore; + +import org.bouncycastle.crypto.digests.RIPEMD160Digest; + +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.math.BigDecimal; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; + +/** + * Static utility methods + */ +public class Utils { + + /** Constant -1 */ + public static final BigInteger NEGATIVE_ONE = BigInteger.valueOf(-1); + + /** Constant 1,000 */ + private static final BigInteger DISPLAY_1K = new BigInteger("1000"); + + /** Constant 1,000,000 */ + private static final BigInteger DISPLAY_1M = new BigInteger("1000000"); + + /** Constant 1,000,000,000 */ + private static final BigInteger DISPLAY_1G = new BigInteger("1000000000"); + + /** Constant 1,000,000,000,000 */ + private static final BigInteger DISPLAY_1T = new BigInteger("1000000000000"); + + /** Constant 1,000,000,000,000,000 */ + private static final BigInteger DISPLAY_1P = new BigInteger("1000000000000000"); + + /** Bit masks (Low-order bit is bit 0 and high-order bit is bit 7) */ + private static final int bitMask[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; + + /** Instance of a SHA-256 digest which we will use as needed */ + private static final MessageDigest digest; + static { + try { + digest = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); // Can't happen. + } + } + + /** + * How many "nanocoins" there are in a Bitcoin. + * + * A nanocoin is the smallest unit that can be transferred using Bitcoin. + * The term nanocoin is very misleading, though, because there are only 100 million + * of them in a coin (whereas one would expect 1 billion. + */ + public static final BigInteger COIN = new BigInteger("100000000", 10); + + /** + * How many "nanocoins" there are in 0.01 BitCoins. + * + * A nanocoin is the smallest unit that can be transferred using Bitcoin. + * The term nanocoin is very misleading, though, because there are only 100 million + * of them in a coin (whereas one would expect 1 billion). + */ + public static final BigInteger CENT = new BigInteger("1000000", 10); + + /** + * Calculate the SHA-256 hash of the input + * + * @param input Data to be hashed + * @return The hash digest + */ + public static byte[] singleDigest(byte[] input) { + return singleDigest(input, 0, input.length); + } + + /** + * Calculate the SHA-256 hash of the input + * + * @param input Data to be hashed + * @param offset Starting offset within the data + * @param length Number of bytes to hash + * @return The hash digest + */ + public static byte[] singleDigest(byte[] input, int offset, int length) { + byte[] bytes; + synchronized (digest) { + digest.reset(); + digest.update(input, offset, length); + bytes = digest.digest(); + } + return bytes; + } + + /** + * Calculate the SHA-256 hash for a list of buffers + * + * @param inputList List of buffers to be hashed + * @return The hash digest + */ + public static byte[] singleDigest(List inputList) { + byte[] bytes; + synchronized(digest) { + digest.reset(); + inputList.stream().forEach((input) -> { + digest.update(input, 0, input.length); + }); + bytes = digest.digest(); + } + return bytes; + } + + /** + * Calculate the SHA-256 hash of the input and then hash the resulting hash again + * + * @param input Data to be hashed + * @return The hash digest + */ + public static byte[] doubleDigest(byte[] input) { + return doubleDigest(input, 0, input.length); + } + + /** + * Calculate the SHA-256 hash of the input and then hash the resulting hash again + * + * @param input Data to be hashed + * @param offset Starting offset within the data + * @param length Number of data bytes to hash + * @return The hash digest + */ + public static byte[] doubleDigest(byte[] input, int offset, int length) { + byte[] bytes; + synchronized (digest) { + digest.reset(); + digest.update(input, offset, length); + byte[] first = digest.digest(); + bytes = digest.digest(first); + } + return bytes; + } + + /** + * Calculate the SHA-256 hash of the input list and then hash the resulting hash again + * + * @param inputList Data to be hashed + * @return The hash digest + */ + public static byte[] doubleDigest(List inputList) { + byte[] bytes; + synchronized(digest) { + digest.reset(); + inputList.stream().forEach((input) -> { + digest.update(input, 0, input.length); + }); + byte[] first = digest.digest(); + bytes = digest.digest(first); + } + return bytes; + } + + /** + * Calculate SHA256(SHA256(byte range 1 + byte range 2)). + * + * @param input1 First input byte array + * @param offset1 Starting position in the first array + * @param length1 Number of bytes to process in the first array + * @param input2 Second input byte array + * @param offset2 Starting position in the second array + * @param length2 Number of bytes to process in the second array + * @return The SHA-256 digest + */ + public static byte[] doubleDigestTwoBuffers(byte[]input1, int offset1, int length1, + byte[]input2, int offset2, int length2) { + byte[] bytes; + synchronized (digest) { + digest.reset(); + digest.update(input1, offset1, length1); + digest.update(input2, offset2, length2); + byte[]first = digest.digest(); + bytes = digest.digest(first); + } + return bytes; + } + + /** + * Calculate the SHA-1 hash of the input + * + * @param input The byte array to be hashed + * @return The hashed result + */ + public static byte[] sha1Hash(byte[] input) { + byte[] out; + try { + MessageDigest sDigest = MessageDigest.getInstance("SHA-1"); + out = sDigest.digest(input); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); // Cannot happen + } + return out; + } + + /** + * Calculate the RIPEMD160 hash of the input + * + * @param input The byte array to be hashed + * @return The hashed result + */ + public static byte[] hash160(byte[] input) { + byte[] out = new byte[20]; + RIPEMD160Digest rDigest = new RIPEMD160Digest(); + rDigest.update(input, 0, input.length); + rDigest.doFinal(out, 0); + return out; + } + + /** + * Calculate RIPEMD160(SHA256(input)). This is used in Address calculations. + * + * @param input The byte array to be hashed + * @return The hashed result + */ + public static byte[] sha256Hash160(byte[] input) { + byte[] out = new byte[20]; + synchronized(digest) { + digest.reset(); + byte[] sha256 = digest.digest(input); + RIPEMD160Digest rDigest = new RIPEMD160Digest(); + rDigest.update(sha256, 0, sha256.length); + rDigest.doFinal(out, 0); + } + return out; + } + + /** + * Return the given byte array encoded as a hex string + * + * @param bytes The data to be encoded + * @return The encoded string + */ + public static String bytesToHexString(byte[] bytes) { + StringBuilder buf = new StringBuilder(bytes.length*2); + for (byte b : bytes) { + String s = Integer.toString(0xFF&b, 16); + if (s.length() < 2) + buf.append('0'); + buf.append(s); + } + return buf.toString(); + } + + /** + * Converts a BigInteger to a fixed-length byte array. + * + * The regular BigInteger method isn't quite what we often need: it appends a + * leading zero to indicate that the number is positive and it may need padding. + * + * @param bigInteger Integer to format into a byte array + * @param numBytes Desired size of the resulting byte array + * @return Byte array of the desired length + */ + public static byte[] bigIntegerToBytes(BigInteger bigInteger, int numBytes) { + if (bigInteger == null) + return null; + byte[] bigBytes = bigInteger.toByteArray(); + byte[] bytes = new byte[numBytes]; + int start = (bigBytes.length==numBytes+1) ? 1 : 0; + int length = Math.min(bigBytes.length, numBytes); + System.arraycopy(bigBytes, start, bytes, numBytes-length, length); + return bytes; + } + + /** + * Returns a string representing the shortened numeric value. For example, + * the value 1,500,000 will be returned as 1.500M. + * + * @param number The number to be displayed + * @return Display string + */ + public static String numberToShortString(BigInteger number) { + int scale; + String suffix; + BigDecimal work; + if (number.compareTo(DISPLAY_1P) >= 0) { + scale = 15; + suffix = "P"; + } else if (number.compareTo(DISPLAY_1T) >= 0) { + scale = 12; + suffix = "T"; + } else if (number.compareTo(DISPLAY_1G) >= 0) { + scale = 9; + suffix = "G"; + } else if (number.compareTo(DISPLAY_1M) >= 0) { + scale = 6; + suffix = "M"; + } else if (number.compareTo(DISPLAY_1K) >= 0) { + scale = 3; + suffix = "K"; + } else { + scale = 0; + suffix = ""; + } + if (scale != 0) + work = new BigDecimal(number, scale); + else + work = new BigDecimal(number); + + return String.format("%3.3f%s", work.floatValue(), suffix); + } + + /** + * Checks if the specified bit is set + * + * @param data Byte array to check + * @param index Bit position + * @return TRUE if the bit is set + */ + public static boolean checkBitLE(byte[] data, int index) { + return (data[index>>>3] & bitMask[7&index]) != 0; + } + + /** + * Sets the specified bit + * @param data Byte array + * @param index Bit position + */ + public static void setBitLE(byte[] data, int index) { + data[index>>>3] |= bitMask[7&index]; + } + + /** + * The representation of nBits uses another home-brew encoding, as a way to represent a large + * hash value in only 32 bits. + * + * @param compact The compact bit representation + * @return The decoded result + */ + public static BigInteger decodeCompactBits(long compact) { + int size = ((int)(compact>>24)) & 0xFF; + byte[] bytes = new byte[4 + size]; + bytes[3] = (byte)size; + if (size>=1) bytes[4] = (byte)((compact>>16) & 0xFF); + if (size>=2) bytes[5] = (byte)((compact>>8) & 0xFF); + if (size>=3) bytes[6] = (byte)(compact & 0xFF); + return decodeMPI(bytes, true); + } + + /** + * MPI encoded numbers are produced by the OpenSSL BN_bn2mpi function. They consist of + * a 4 byte big-endian length field, followed by the stated number of bytes representing + * the number in big-endian format (with a sign bit). + * + * @param mpi Encoded byte array + * @param hasLength FALSE if the given array is missing the 4-byte length field + * @return Decoded value + */ + public static BigInteger decodeMPI(byte[] mpi, boolean hasLength) { + byte[] buf; + if (hasLength) { + int length = (int)readUint32BE(mpi, 0); + buf = new byte[length]; + System.arraycopy(mpi, 4, buf, 0, length); + } else { + buf = mpi; + } + if (buf.length == 0) + return BigInteger.ZERO; + boolean isNegative = (buf[0] & 0x80) == 0x80; + if (isNegative) + buf[0] &= 0x7f; + BigInteger result = new BigInteger(buf); + return isNegative ? result.negate() : result; + } + + /** + * MPI encoded numbers are produced by the OpenSSL BN_bn2mpi function. They consist of + * a 4 byte big endian length field, followed by the stated number of bytes representing + * the number in big endian format as a positive number with a sign bit. + * + * @param value BigInteger value + * @param includeLength TRUE to include the 4 byte length field + * @return Encoded value + */ + public static byte[] encodeMPI(BigInteger value, boolean includeLength) { + byte[] bytes; + if (value.equals(BigInteger.ZERO)) { + if (!includeLength) + bytes = new byte[] {}; + else + bytes = new byte[] {0x00, 0x00, 0x00, 0x00}; + } else { + boolean isNegative = value.signum()<0; + if (isNegative) + value = value.negate(); + byte[] array = value.toByteArray(); + int length = array.length; + if ((array[0]&0x80) == 0x80) + length++; + if (includeLength) { + bytes = new byte[length+4]; + System.arraycopy(array, 0, bytes, length-array.length+3, array.length); + uint32ToByteArrayBE(length, bytes, 0); + if (isNegative) + bytes[4] |= 0x80; + } else { + if (length != array.length) { + bytes = new byte[length]; + System.arraycopy(array, 0, bytes, 1, array.length); + } else { + bytes = array; + } + if (isNegative) + bytes[0] |= 0x80; + } + } + return bytes; + } + + /** + * Returns a copy of the given byte array in reverse order. + * + * @param bytes Array to be reversed + * @return New byte array in reverse order + */ + public static byte[] reverseBytes(byte[] bytes) { + byte[] buf = new byte[bytes.length]; + for (int i=0; i= 0 && bytes.length > trimLength ? trimLength : bytes.length]; + for (int i = 0; i < rev.length; i += 4) { + System.arraycopy(bytes, i, rev, i , 4); + for (int j = 0; j < 4; j++) { + rev[i + j] = bytes[i + 3 - j]; + } + } + return rev; + } + + /** + * Form a long value from a 4-byte array in little-endian format + * + * @param bytes The byte array + * @param offset Starting offset within the array + * @return The decoded value + */ + public static long readUint32LE(byte[] bytes, int offset) { + return ((long)bytes[offset++]&0x00FFL) | + (((long)bytes[offset++]&0x00FFL) << 8) | + (((long)bytes[offset++]&0x00FFL) << 16) | + (((long)bytes[offset]&0x00FFL) << 24); + } + + /** + * Form a long value from a 4-byte array in big-endian format + * + * @param bytes The byte array + * @param offset Starting offset within the array + * @return The long value + */ + public static long readUint32BE(byte[] bytes, int offset) { + return (((long)bytes[offset++]&0x00FFL) << 24) | + (((long)bytes[offset++]&0x00FFL) << 16) | + (((long)bytes[offset++]&0x00FFL) << 8) | + ((long)bytes[offset]&0x00FFL); + } + + /** + * Write an unsigned 32-bit value to a byte array in little-endian format + * + * @param val Value to be written + * @param out Output array + * @param offset Starting offset + */ + public static void uint32ToByteArrayLE(long val, byte[] out, int offset) { + out[offset++] = (byte)val; + out[offset++] = (byte)(val >> 8); + out[offset++] = (byte)(val >> 16); + out[offset] = (byte)(val >> 24); + } + + /** + * Write an unsigned 32-bit value to a byte array in big-endian format + * + * @param val Value to be written + * @param out Output array + * @param offset Starting offset + */ + public static void uint32ToByteArrayBE(long val, byte[] out, int offset) { + out[offset++] = (byte)(val>>24); + out[offset++] = (byte)(val>>16); + out[offset++] = (byte)(val>>8); + out[offset] = (byte)val; + } + + /** + * Form a long value from an 8-byte array in little-endian format + * + * @param bytes The byte array + * @param offset Starting offset within the array + * @return The long value + */ + public static long readUint64LE(byte[] bytes, int offset) { + return ((long)bytes[offset++]&0x00FFL) | + (((long)bytes[offset++]&0x00FFL) << 8) | + (((long)bytes[offset++]&0x00FFL) << 16) | + (((long)bytes[offset++]&0x00FFL) << 24) | + (((long)bytes[offset++]&0x00FFL) << 32) | + (((long)bytes[offset++]&0x00FFL) << 40) | + (((long)bytes[offset++]&0x00FFL) << 48) | + (((long)bytes[offset]&0x00FFL) << 56); + } + + /** + * Write an unsigned 64-bit value to a byte array in little-endian format + * + * @param val Value to be written + * @param out Output array + * @param offset Starting offset + */ + public static void uint64ToByteArrayLE(long val, byte[] out, int offset) { + out[offset++] = (byte)val; + out[offset++] = (byte)(val >> 8); + out[offset++] = (byte)(val >> 16); + out[offset++] = (byte)(val >> 24); + out[offset++] = (byte)(val >> 32); + out[offset++] = (byte)(val >> 40); + out[offset++] = (byte)(val >> 48); + out[offset] = (byte)(val >> 56); + } +} diff --git a/src/main/java/org/toporin/bitcoincore/VarInt.java b/src/main/java/org/toporin/bitcoincore/VarInt.java new file mode 100644 index 0000000..408978d --- /dev/null +++ b/src/main/java/org/toporin/bitcoincore/VarInt.java @@ -0,0 +1,265 @@ +/** + * Copyright 2011 Google Inc. + * Copyright 2013 Ronald W Hoffman + * + * 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.toporin.bitcoincore; + +import java.io.EOFException; +import java.io.InputStream; +import java.io.IOException; + +/** + * A VarInt is an unsigned variable-length encoded integer using the bitcoin encoding (called the 'compact size' + * in the reference client). It consists of a marker byte and zero or more data bytes as follows: + *
+ *      Value           Marker      Data
+ *      ======          ======      ====
+ *      0-252           0-252       0 bytes
+ *      253 to 2^16-1   253         2 bytes
+ *      2^16 to 2^32-1  254         4 bytes
+ *      2^32 to 2^64-1  255         8 bytes
+ * 
+ */ +public final class VarInt { + + /** The value of this VarInt */ + private final long value; + + /** The encoded size of this VarInt */ + private int encodedSize; + + /** + * Creates a new VarInt with the requested value + * + * @param value Requested value + */ + public VarInt(long value) { + this.value = value; + encodedSize = sizeOf(value); + } + + /** + * Creates a new VarInt from a byte array in little-endian format + * + * @param buf Byte array + * @param offset Starting offset into the array + * @throws EOFException Buffer is too small + */ + public VarInt(byte[]buf, int offset) throws EOFException { + if (offset > buf.length) + throw new EOFException("End-of-data while processing VarInt"); + int first = 0x00FF&(int)buf[offset]; + if (first < 253) { + // 8 bits. + value = first; + encodedSize = 1; + } else if (first == 253) { + // 16 bits. + if (offset+2 > buf.length) + throw new EOFException("End-of-data while processing VarInt"); + value = (0x00FF&(int)buf[offset+1]) | ((0x00FF&(int)buf[offset+2])<<8); + encodedSize = 3; + } else if (first == 254) { + // 32 bits. + if (offset+5 > buf.length) + throw new EOFException("End-of-data while processing VarInt"); + value = Utils.readUint32LE(buf, offset+1); + encodedSize = 5; + } else { + // 64 bits. + if (offset+9 > buf.length) + throw new EOFException("End-of-data while processing VarInt"); + value = Utils.readUint64LE(buf, offset+1); + encodedSize = 9; + } + } + + /** + * Creates a new VarInt from an input stream encoded in little-endian format + * + * @param in Input stream + * @throws EOFException End-of-data processing stream + * @throws IOException I/O error processing stream + */ + public VarInt(InputStream in) throws EOFException, IOException { + int count; + int first = in.read(); + if (first < 0) + throw new EOFException("End-of-data while processing VarInt"); + + if (first < 253) { + // 8 bits. + value = first; + encodedSize = 1; + } else if (first == 253) { + // 16 bits. + byte[] buf = new byte[2]; + count = in.read(buf, 0, 2); + if (count < 2) + throw new EOFException("End-of-data while processing VarInt"); + + value = (0x00FF&(int)buf[0]) | ((0x00FF&(int)buf[1])<<8); + encodedSize = 3; + } else if (first == 254) { + // 32 bits. + byte[] buf = new byte[4]; + count = in.read(buf, 0, 4); + if (count < 4) + throw new EOFException("End-of-data while processing VarInt"); + + value = Utils.readUint32LE(buf, 0); + encodedSize = 5; + } else { + // 64 bits. + byte[] buf = new byte[8]; + count = in.read(buf, 0, 8); + if (count < 8) + throw new EOFException("End-of-data while processing VarInt"); + + value = Utils.readUint64LE(buf, 0); + encodedSize = 9; + } + } + + /** + * Returns the value of thie VarInt as an int + * + * @return Integer value + */ + public int toInt() { + return (int)value; + } + + /** + * Returns the value of this VarInt as a long + * + * @return Long value + */ + public long toLong() { + return value; + } + + /** + * Returns the encoded size of this VarInt + * + * @return Encoded size + */ + public int getEncodedSize() { + return encodedSize; + } + + /** + * Returns the encoded VarInt size + * + * @param bytes Encoded byte stream + * @param offset Offset of the encoded VarInt + * @return Encoded size + */ + public static int sizeOf(byte[] bytes, int offset) { + int length; + int varLength = (int)bytes[offset]&0xff; + if (varLength < 253) + length = 1; + else if (varLength == 253) + length = 3; + else if (varLength == 254) + length = 5; + else + length = 9; + return length; + } + + /** + * Returns the encoded size of the given unsigned integer value. + * + * @param value Value to be encoded + * @return Encoded size + */ + public static int sizeOf(int value) { + int minSize; + long tValue = ((long)value)&0xffffffffL; + + if (tValue < 253L) + minSize = 1; // Single data byte + else if (tValue < 65536L) + minSize = 3; // 1 marker + 2 data bytes + else + minSize = 5; // 1 marker + 4 data bytes + + return minSize; + } + + /** + * Returns the encoded size of the given unsigned long value + * + * @param value Value to be encoded + * @return Encoded size + */ + public static int sizeOf(long value) { + int minSize; + if ((value&0xFFFFFFFF00000000L) != 0) { + // 1 marker + 8 data bytes + minSize = 9; + } else if ((value&0x00000000FFFF0000L) != 0) { + // 1 marker + 4 data bytes + minSize = 5; + } else if (value >= 253L) { + // 1 marker + 2 data bytes + minSize = 3; + } else { + // Single data byte + minSize = 1; + } + + return minSize; + } + + /** + * Encode the value in little-endian format + * + * @return Encoded byte stream + */ + public byte[] encode() { + return encode(value); + } + + /** + * Encode the value in little-endian format + * + * @param value Value to encode + * @return Byte array + */ + public static byte[] encode(long value) { + byte[] bytes; + if ((value&0xFFFFFFFF00000000L) != 0) { + // 1 marker + 8 data bytes + bytes = new byte[9]; + bytes[0] = (byte)255; + Utils.uint64ToByteArrayLE(value, bytes, 1); + } else if ((value&0x00000000FFFF0000L) != 0) { + // 1 marker + 4 data bytes + bytes = new byte[5]; + bytes[0] = (byte)254; + Utils.uint32ToByteArrayLE(value, bytes, 1); + } else if (value >= 253L) { + // 1 marker + 2 data bytes + bytes = new byte[]{(byte)253, (byte)value, (byte)(value>>8)}; + } else { + // Single data byte + bytes = new byte[]{(byte)value}; + } + return bytes; + } +} diff --git a/src/test/java/org/satochip/satochipclient/CardConnectorTest.java b/src/test/java/org/satochip/satochipclient/CardConnectorTest.java new file mode 100644 index 0000000..c207871 --- /dev/null +++ b/src/test/java/org/satochip/satochipclient/CardConnectorTest.java @@ -0,0 +1,1056 @@ +/* + * 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 outputs = (ArrayList) 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(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(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 inputs = (ArrayList) 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(); +// 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 msg_list= new ArrayList<>(); + ArrayList 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= new ArrayList<>(); + ArrayList 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