implement GET STATUS

This commit is contained in:
Michele Balistreri 2017-10-09 17:12:21 +03:00
parent f4ea1f6e69
commit 5ba012fad1
4 changed files with 118 additions and 8 deletions

View File

@ -55,6 +55,22 @@ be used by the client to establish the Secure Channel.
The OPEN SECURE CHANNEL command is as specified in the [SECURE_CHANNEL.MD](SECURE_CHANNEL.MD).
### GET STATUS
* CLA = 0x80
* INS = 0xF2
* P1 = 0x00
* P2 = 0x00
* Response SW = 0x9000 on success
* Response Data = Application Status Template
* Preconditions: Secure Channel must be opened
Response Data format:
- Tag 0xA3 = Application Status Template
- Tag 0xC0 = PIN retry count (1 byte)
- Tag 0xC1 = PUK retry count (1 byte)
- Tag 0xC2 = 0 if key is not initialized, 1 otherwise
- Tag 0xC3 = 0 if public key derivation is not supported, 1 otherwise
### VERIFY PIN
* CLA = 0x80
@ -143,14 +159,14 @@ signing sessions, if any. Unless a DERIVE KEY is sent, a subsequent SIGN command
* Data = key derivation template
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid, 0x6A81 if public keys are omitted and their derivation
is not supported.
* Preconditions: Secure Channel must be opened, user PIN must be verified
* Preconditions: Secure Channel must be opened, user PIN must be verified, an extended keyset must be loaded
Data format:
- Tag 0xA2 = key derivation template
- Tag 0x82 = a sequence of 32-bit integers (most significant byte first). Empty if the master key must be used.
- Tag 0x81 = parent public key (omitted if master or public key derivation is supported)
- Tag 0x80 = derived public key (omitted if master or public key derivation is supported)
- Tag 0xC0 = a sequence of 32-bit integers (most significant byte first). Empty if the master key must be used.
- Tag 0xC1 = derived public key (omitted if master or public key derivation is supported)
- Tag 0xC2 = parent public key (omitted if master or public key derivation is supported)
This command is used before a signing session to generated a private key according to the [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
specifications. The generated key is used for all subsequent SIGN sessions. An empty 0x82 is used in order for SIGN to

View File

@ -4,10 +4,13 @@ import javacard.framework.*;
import javacard.security.*;
public class WalletApplet extends Applet {
static final byte INS_GET_STATUS = (byte) 0xF2;
static final byte INS_VERIFY_PIN = (byte) 0x20;
static final byte INS_CHANGE_PIN = (byte) 0x21;
static final byte INS_UNBLOCK_PIN = (byte) 0x22;
static final byte INS_LOAD_KEY = (byte) 0xD0;
static final byte INS_DERIVE_KEY = (byte) 0xD1;
static final byte INS_GENERATE_MNEMONIC = (byte) 0xD2;
static final byte INS_SIGN = (byte) 0xC0;
static final byte PUK_LENGTH = 12;
@ -18,6 +21,8 @@ public class WalletApplet extends Applet {
static final short EC_KEY_SIZE = 256;
static final byte LOAD_KEY_P1_EC = 0x01;
static final byte LOAD_KEY_P1_EXT_EC = 0x02;
static final byte LOAD_KEY_P1_SEED = 0x03;
static final byte SIGN_P1_DATA = 0x00;
static final byte SIGN_P1_PRECOMPUTED_HASH = 0x01;
@ -26,9 +31,22 @@ public class WalletApplet extends Applet {
static final byte SIGN_P2_LAST_BLOCK_MASK = (byte) 0x80;
static final byte TLV_SIGNATURE_TEMPLATE = (byte) 0xA0;
static final byte TLV_KEY_TEMPLATE = (byte) 0xA1;
static final byte TLV_PUB_KEY = (byte) 0x80;
static final byte TLV_PRIV_KEY = (byte) 0x81;
static final byte TLV_CHAIN_CODE = (byte) 0x82;
static final byte TLV_KEY_DERIVATION_TEMPLATE = (byte) 0xA2;
static final byte TLV_DERIVATION_SEQUENCE = (byte) 0xC0;
static final byte TLV_DERIVED_PUB_KEY = (byte) 0xC1;
static final byte TLV_PARENT_PUB_KEY = (byte) 0xC2;
static final byte TLV_APPLICATION_STATUS_TEMPLATE = (byte) 0xA3;
static final byte TLV_PIN_RETRY_COUNT = (byte) 0xC0;
static final byte TLV_PUK_RETRY_COUNT = (byte) 0xC1;
static final byte TLV_KEY_INITIALIZATION_STATUS = (byte) 0xC2;
static final byte TLV_PUBLIC_KEY_DERIVATION_SUPPORTED = (byte) 0xC3;
private OwnerPIN pin;
private OwnerPIN puk;
@ -77,6 +95,9 @@ public class WalletApplet extends Applet {
case SecureChannel.INS_OPEN_SECURE_CHANNEL:
secureChannel.openSecureChannel(apdu);
break;
case INS_GET_STATUS:
getStatus(apdu);
break;
case INS_VERIFY_PIN:
verifyPIN(apdu);
break;
@ -89,6 +110,12 @@ public class WalletApplet extends Applet {
case INS_LOAD_KEY:
loadKey(apdu);
break;
case INS_DERIVE_KEY:
deriveKey(apdu);
break;
case INS_GENERATE_MNEMONIC:
generateMnemonic(apdu);
break;
case INS_SIGN:
sign(apdu);
break;
@ -108,6 +135,33 @@ public class WalletApplet extends Applet {
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, keyLength);
}
private void getStatus(APDU apdu) {
if (!secureChannel.isOpen()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
short off = SecureChannel.SC_OUT_OFFSET;
byte[] apduBuffer = apdu.getBuffer();
apduBuffer[off++] = TLV_APPLICATION_STATUS_TEMPLATE;
apduBuffer[off++] = 12;
apduBuffer[off++] = TLV_PIN_RETRY_COUNT;
apduBuffer[off++] = 1;
apduBuffer[off++] = pin.getTriesRemaining();
apduBuffer[off++] = TLV_PUK_RETRY_COUNT;
apduBuffer[off++] = 1;
apduBuffer[off++] = puk.getTriesRemaining();
apduBuffer[off++] = TLV_KEY_INITIALIZATION_STATUS;
apduBuffer[off++] = 1;
apduBuffer[off++] = privateKey.isInitialized() ? (byte) 0x01 : (byte) 0x00;
apduBuffer[off++] = TLV_PUBLIC_KEY_DERIVATION_SUPPORTED;
apduBuffer[off++] = 1;
apduBuffer[off++] = 1; //TODO: actually check if it is supported or totally remove if a fallback software implementation is a requirement
short len = secureChannel.encryptAPDU(apduBuffer, (short) (off - SecureChannel.SC_OUT_OFFSET));
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, len);
}
private void verifyPIN(APDU apdu) {
apdu.setIncomingAndReceive();
@ -199,6 +253,14 @@ public class WalletApplet extends Applet {
signInProgress = false;
}
private void deriveKey(APDU apdu) {
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
private void generateMnemonic(APDU apdu) {
ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
}
private void sign(APDU apdu) {
apdu.setIncomingAndReceive();

View File

@ -36,6 +36,11 @@ public class WalletAppletCommandSet {
return secureChannel.openSecureChannel(apduChannel);
}
public ResponseAPDU getStatus() throws CardException {
CommandAPDU getStatus = new CommandAPDU(0x80, WalletApplet.INS_GET_STATUS, 0, 0);
return apduChannel.transmit(getStatus);
}
public ResponseAPDU verifyPIN(String pin) throws CardException {
CommandAPDU verifyPIN = new CommandAPDU(0x80, WalletApplet.INS_VERIFY_PIN, 0, 0, secureChannel.encryptAPDU(pin.getBytes()));
return apduChannel.transmit(verifyPIN);

View File

@ -12,7 +12,6 @@ import org.web3j.crypto.*;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.request.RawTransaction;
import org.web3j.protocol.core.methods.response.EthGetTransactionReceipt;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.Transfer;
@ -106,6 +105,36 @@ public class WalletAppletTest {
assertEquals(SecureChannel.SC_SECRET_LENGTH, response.getData().length);
}
@Test
@DisplayName("GET STATUS command")
void getStatusTest() throws CardException {
// Security condition violation: SecureChannel not open
ResponseAPDU response = cmdSet.getStatus();
assertEquals(0x6985, response.getSW());
cmdSet.openSecureChannel();
// Good case. Since the order of test execution is undefined, the test cannot know if the keys are initialized or not.
// Additionally, the public key derivation cannot also be known here.
response = cmdSet.getStatus();
assertEquals(0x9000, response.getSW());
byte[] data = secureChannel.decryptAPDU(response.getData());
assertTrue(Hex.toHexString(data).matches("a30cc00103c10105c2010[0-1]c3010[0-1]"));
response = cmdSet.verifyPIN("123456");
assertEquals(0x63C2, response.getSW());
response = cmdSet.getStatus();
assertEquals(0x9000, response.getSW());
data = secureChannel.decryptAPDU(response.getData());
assertTrue(Hex.toHexString(data).matches("a30cc00102c10105c2010[0-1]c3010[0-1]"));
response = cmdSet.verifyPIN("000000");
assertEquals(0x9000, response.getSW());
response = cmdSet.getStatus();
assertEquals(0x9000, response.getSW());
data = secureChannel.decryptAPDU(response.getData());
assertTrue(Hex.toHexString(data).matches("a30cc00103c10105c2010[0-1]c3010[0-1]"));
}
@Test
@DisplayName("VERIFY PIN command")
void verifyPinTest() throws CardException {
@ -442,8 +471,6 @@ public class WalletAppletTest {
}
assertFalse(ethSendTransaction.hasError());
EthGetTransactionReceipt receipt = web3j.ethGetTransactionReceipt(ethSendTransaction.getTransactionHash()).send();
}
private KeyPairGenerator keypairGenerator() throws Exception {
@ -481,7 +508,7 @@ public class WalletAppletTest {
cmdSet.openSecureChannel();
}
public Sign.SignatureData signMessage(byte[] message) throws Exception {
private Sign.SignatureData signMessage(byte[] message) throws Exception {
byte[] messageHash = Hash.sha3(message);
ResponseAPDU response = cmdSet.sign(messageHash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true);