implement GENERATE MNEMONIC
This commit is contained in:
parent
5ba012fad1
commit
9396e1448d
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue