implement GENERATE MNEMONIC

This commit is contained in:
Michele Balistreri 2017-10-10 20:07:09 +03:00
parent 5ba012fad1
commit 9396e1448d
5 changed files with 127 additions and 10 deletions

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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) {

View File

@ -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));

View File

@ -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);