From 9396e1448d7d2f25271dad0a27e1c99312a147ca Mon Sep 17 00:00:00 2001 From: Michele Balistreri Date: Tue, 10 Oct 2017 20:07:09 +0300 Subject: [PATCH] implement GENERATE MNEMONIC --- src/main/java/im/status/wallet/Crypto.java | 15 ++++++ .../java/im/status/wallet/SecureChannel.java | 13 ++--- .../java/im/status/wallet/WalletApplet.java | 50 ++++++++++++++++- .../status/wallet/WalletAppletCommandSet.java | 5 ++ .../im/status/wallet/WalletAppletTest.java | 54 +++++++++++++++++++ 5 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 src/main/java/im/status/wallet/Crypto.java diff --git a/src/main/java/im/status/wallet/Crypto.java b/src/main/java/im/status/wallet/Crypto.java new file mode 100644 index 0000000..18ceba9 --- /dev/null +++ b/src/main/java/im/status/wallet/Crypto.java @@ -0,0 +1,15 @@ +package im.status.wallet; + +import javacard.security.MessageDigest; +import javacard.security.RandomData; + +public class Crypto { + public static RandomData random; + public static MessageDigest sha256; + + + public static void init() { + random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); + sha256 = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false); + } +} diff --git a/src/main/java/im/status/wallet/SecureChannel.java b/src/main/java/im/status/wallet/SecureChannel.java index 28ed39c..beefec1 100644 --- a/src/main/java/im/status/wallet/SecureChannel.java +++ b/src/main/java/im/status/wallet/SecureChannel.java @@ -19,14 +19,9 @@ public class SecureChannel { private AESKey scKey; private Cipher scCipher; private KeyPair scKeypair; - private MessageDigest scMd; - private RandomData scRandom; private byte[] secret; public SecureChannel() { - scRandom = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); - scMd = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false); - scCipher = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); scKey = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_AES_256, false); @@ -46,9 +41,9 @@ public class SecureChannel { apdu.setIncomingAndReceive(); byte[] apduBuffer = apdu.getBuffer(); short len = scAgreement.generateSecret(apduBuffer, ISO7816.OFFSET_CDATA, apduBuffer[ISO7816.OFFSET_LC], secret, (short) 0); - scRandom.generateData(apduBuffer, (short) 0, SC_SECRET_LENGTH); - scMd.update(secret, (short) 0, len); - scMd.doFinal(apduBuffer, (short) 0, SC_SECRET_LENGTH, secret, (short) 0); + Crypto.random.generateData(apduBuffer, (short) 0, SC_SECRET_LENGTH); + Crypto.sha256.update(secret, (short) 0, len); + Crypto.sha256.doFinal(apduBuffer, (short) 0, SC_SECRET_LENGTH, secret, (short) 0); scKey.setKey(secret, (short) 0); apdu.setOutgoingAndSend((short) 0, SC_SECRET_LENGTH); } @@ -77,7 +72,7 @@ public class SecureChannel { Util.arrayFillNonAtomic(apduBuffer, (short)(SC_OUT_OFFSET + len), padding, (byte) 0x00); len += padding; - scRandom.generateData(apduBuffer, ISO7816.OFFSET_CDATA, SC_BLOCK_SIZE); + Crypto.random.generateData(apduBuffer, ISO7816.OFFSET_CDATA, SC_BLOCK_SIZE); scCipher.init(scKey, Cipher.MODE_ENCRYPT, apduBuffer, ISO7816.OFFSET_CDATA, SC_BLOCK_SIZE); len = scCipher.doFinal(apduBuffer, SC_OUT_OFFSET, len, apduBuffer, (short)(ISO7816.OFFSET_CDATA + SC_BLOCK_SIZE)); return (short)(len + SC_BLOCK_SIZE); diff --git a/src/main/java/im/status/wallet/WalletApplet.java b/src/main/java/im/status/wallet/WalletApplet.java index d00247f..489f670 100644 --- a/src/main/java/im/status/wallet/WalletApplet.java +++ b/src/main/java/im/status/wallet/WalletApplet.java @@ -30,6 +30,10 @@ public class WalletApplet extends Applet { static final byte SIGN_P2_FIRST_BLOCK_MASK = 0x01; static final byte SIGN_P2_LAST_BLOCK_MASK = (byte) 0x80; + static final byte GENERATE_MNEMONIC_P1_CS_MIN = 4; + static final byte GENERATE_MNEMONIC_P1_CS_MAX = 8; + static final byte GENERATE_MNEMONIC_TMP_OFF = SecureChannel.SC_OUT_OFFSET + ((((GENERATE_MNEMONIC_P1_CS_MAX * 32) + GENERATE_MNEMONIC_P1_CS_MAX) / 11) * 2); + static final byte TLV_SIGNATURE_TEMPLATE = (byte) 0xA0; static final byte TLV_KEY_TEMPLATE = (byte) 0xA1; @@ -61,6 +65,8 @@ public class WalletApplet extends Applet { } public WalletApplet(byte[] bArray, short bOffset, byte bLength) { + Crypto.init(); + short c9Off = (short)(bOffset + bArray[bOffset] + 1); // Skip AID c9Off += (short)(bArray[c9Off] + 2); // Skip Privileges and parameter length @@ -220,6 +226,8 @@ public class WalletApplet extends Applet { } private void loadKey(APDU apdu) { + apdu.setIncomingAndReceive(); + if (!(secureChannel.isOpen() && pin.isValidated())) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -258,7 +266,47 @@ public class WalletApplet extends Applet { } private void generateMnemonic(APDU apdu) { - ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED); + if (!secureChannel.isOpen()) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + + byte[] apduBuffer = apdu.getBuffer(); + short csLen = apduBuffer[ISO7816.OFFSET_P1]; + + if (csLen < GENERATE_MNEMONIC_P1_CS_MIN || csLen > GENERATE_MNEMONIC_P1_CS_MAX) { + ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); + } + + short entLen = (short) (csLen * 4); + Crypto.random.generateData(apduBuffer, GENERATE_MNEMONIC_TMP_OFF, entLen); + Crypto.sha256.doFinal(apduBuffer, GENERATE_MNEMONIC_TMP_OFF, entLen, apduBuffer, (short)(GENERATE_MNEMONIC_TMP_OFF + entLen)); + entLen += GENERATE_MNEMONIC_TMP_OFF + 1; + + short outOff = SecureChannel.SC_OUT_OFFSET; + short rShift = 0; + short vp = 0; + + for (short i = GENERATE_MNEMONIC_TMP_OFF; i < entLen; i += 2) { + short w = Util.getShort(apduBuffer, i); + Util.setShort(apduBuffer, outOff, (short)((short)(((short)(vp | ((short) (w >>> rShift)))) >>> 5) & (short) 0x7ff)); + outOff += 2; + rShift += 5; + vp = (short) (w << (16 - rShift)); + + if (rShift >= 11) { + Util.setShort(apduBuffer, outOff, (short)((short) (vp >>> 5) & (short) 0x7ff)); + outOff += 2; + rShift = (short) (rShift - 11); + vp = (short) (w << (16 - rShift)); + } + } + + if (csLen < 6) { + outOff -= 2; // a last spurious 11 bit number will be generated when cs length is less than 6 because 16 - cs >= 11 + } + + short outLen = secureChannel.encryptAPDU(apduBuffer, (short) (outOff - SecureChannel.SC_OUT_OFFSET)); + apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, outLen); } private void sign(APDU apdu) { diff --git a/src/test/java/im/status/wallet/WalletAppletCommandSet.java b/src/test/java/im/status/wallet/WalletAppletCommandSet.java index 7d42bfd..323c2b8 100644 --- a/src/test/java/im/status/wallet/WalletAppletCommandSet.java +++ b/src/test/java/im/status/wallet/WalletAppletCommandSet.java @@ -120,6 +120,11 @@ public class WalletAppletCommandSet { return apduChannel.transmit(loadKey); } + public ResponseAPDU generateMnemonic(int cs) throws CardException { + CommandAPDU generateMnemonic = new CommandAPDU(0x80, WalletApplet.INS_GENERATE_MNEMONIC, cs, 0); + return apduChannel.transmit(generateMnemonic); + } + public ResponseAPDU sign(byte[] data, byte dataType, boolean isFirst, boolean isLast) throws CardException { byte p2 = (byte) ((isFirst ? 0x01 : 0x00) | (isLast ? 0x80 : 0x00)); CommandAPDU sign = new CommandAPDU(0x80, WalletApplet.INS_SIGN, dataType, p2, secureChannel.encryptAPDU(data)); diff --git a/src/test/java/im/status/wallet/WalletAppletTest.java b/src/test/java/im/status/wallet/WalletAppletTest.java index 6cb10ab..64b9d66 100644 --- a/src/test/java/im/status/wallet/WalletAppletTest.java +++ b/src/test/java/im/status/wallet/WalletAppletTest.java @@ -24,6 +24,8 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.security.*; import java.util.Arrays; import java.util.Random; @@ -309,6 +311,43 @@ public class WalletAppletTest { assertEquals(0x9000, response.getSW()); } + @Test + @DisplayName("GENERATE MNEMONIC command") + void generateMnemonicTest() throws Exception { + // Security condition violation: SecureChannel not open + ResponseAPDU response = cmdSet.getStatus(); + assertEquals(0x6985, response.getSW()); + cmdSet.openSecureChannel(); + + // Wrong P1 (too short, too long) + response = cmdSet.generateMnemonic(3); + assertEquals(0x6A86, response.getSW()); + + response = cmdSet.generateMnemonic(9); + assertEquals(0x6A86, response.getSW()); + + // Good cases + response = cmdSet.generateMnemonic(4); + assertEquals(0x9000, response.getSW()); + assertMnemonic(12, secureChannel.decryptAPDU(response.getData())); + + response = cmdSet.generateMnemonic(5); + assertEquals(0x9000, response.getSW()); + assertMnemonic(15, secureChannel.decryptAPDU(response.getData())); + + response = cmdSet.generateMnemonic(6); + assertEquals(0x9000, response.getSW()); + assertMnemonic(18, secureChannel.decryptAPDU(response.getData())); + + response = cmdSet.generateMnemonic(7); + assertEquals(0x9000, response.getSW()); + assertMnemonic(21, secureChannel.decryptAPDU(response.getData())); + + response = cmdSet.generateMnemonic(8); + assertEquals(0x9000, response.getSW()); + assertMnemonic(24, secureChannel.decryptAPDU(response.getData())); + } + @Test @DisplayName("SIGN command") void signTest() throws Exception { @@ -508,6 +547,21 @@ public class WalletAppletTest { cmdSet.openSecureChannel(); } + private void assertMnemonic(int expectedLength, byte[] data) { + short[] shorts = new short[data.length/2]; + assertEquals(expectedLength, shorts.length); + ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get(shorts); + + for (short mIdx : shorts) { + assertTrue(mIdx >= 0 && mIdx < 2048); + } + + // TODO: the checksum should be validated. The problem is that the simulator should generate wrong values because of + // the bitwise operator extends the type to int, while JavaCard does not support int at all. If we make it work on + // the simulator then the code will not convert to CAP file at all. This means that the checksum can be tested only + // on a real card. + } + private Sign.SignatureData signMessage(byte[] message) throws Exception { byte[] messageHash = Hash.sha3(message);