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). 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 ### VERIFY PIN
* CLA = 0x80 * CLA = 0x80
@ -143,14 +159,14 @@ signing sessions, if any. Unless a DERIVE KEY is sent, a subsequent SIGN command
* Data = key derivation template * Data = key derivation template
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid, 0x6A81 if public keys are omitted and their derivation * Response SW = 0x9000 on success, 0x6A80 if the format is invalid, 0x6A81 if public keys are omitted and their derivation
is not supported. 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: Data format:
- Tag 0xA2 = key derivation template - 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 0xC0 = 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 0xC1 = derived 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 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) 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 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.*; import javacard.security.*;
public class WalletApplet extends Applet { 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_VERIFY_PIN = (byte) 0x20;
static final byte INS_CHANGE_PIN = (byte) 0x21; static final byte INS_CHANGE_PIN = (byte) 0x21;
static final byte INS_UNBLOCK_PIN = (byte) 0x22; static final byte INS_UNBLOCK_PIN = (byte) 0x22;
static final byte INS_LOAD_KEY = (byte) 0xD0; 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 INS_SIGN = (byte) 0xC0;
static final byte PUK_LENGTH = 12; static final byte PUK_LENGTH = 12;
@ -18,6 +21,8 @@ public class WalletApplet extends Applet {
static final short EC_KEY_SIZE = 256; static final short EC_KEY_SIZE = 256;
static final byte LOAD_KEY_P1_EC = 0x01; 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_DATA = 0x00;
static final byte SIGN_P1_PRECOMPUTED_HASH = 0x01; 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 SIGN_P2_LAST_BLOCK_MASK = (byte) 0x80;
static final byte TLV_SIGNATURE_TEMPLATE = (byte) 0xA0; static final byte TLV_SIGNATURE_TEMPLATE = (byte) 0xA0;
static final byte TLV_KEY_TEMPLATE = (byte) 0xA1; static final byte TLV_KEY_TEMPLATE = (byte) 0xA1;
static final byte TLV_PUB_KEY = (byte) 0x80; static final byte TLV_PUB_KEY = (byte) 0x80;
static final byte TLV_PRIV_KEY = (byte) 0x81; 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 pin;
private OwnerPIN puk; private OwnerPIN puk;
@ -77,6 +95,9 @@ public class WalletApplet extends Applet {
case SecureChannel.INS_OPEN_SECURE_CHANNEL: case SecureChannel.INS_OPEN_SECURE_CHANNEL:
secureChannel.openSecureChannel(apdu); secureChannel.openSecureChannel(apdu);
break; break;
case INS_GET_STATUS:
getStatus(apdu);
break;
case INS_VERIFY_PIN: case INS_VERIFY_PIN:
verifyPIN(apdu); verifyPIN(apdu);
break; break;
@ -89,6 +110,12 @@ public class WalletApplet extends Applet {
case INS_LOAD_KEY: case INS_LOAD_KEY:
loadKey(apdu); loadKey(apdu);
break; break;
case INS_DERIVE_KEY:
deriveKey(apdu);
break;
case INS_GENERATE_MNEMONIC:
generateMnemonic(apdu);
break;
case INS_SIGN: case INS_SIGN:
sign(apdu); sign(apdu);
break; break;
@ -108,6 +135,33 @@ public class WalletApplet extends Applet {
apdu.setOutgoingAndSend(ISO7816.OFFSET_CDATA, keyLength); 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) { private void verifyPIN(APDU apdu) {
apdu.setIncomingAndReceive(); apdu.setIncomingAndReceive();
@ -199,6 +253,14 @@ public class WalletApplet extends Applet {
signInProgress = false; 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) { private void sign(APDU apdu) {
apdu.setIncomingAndReceive(); apdu.setIncomingAndReceive();

View File

@ -36,6 +36,11 @@ public class WalletAppletCommandSet {
return secureChannel.openSecureChannel(apduChannel); 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 { public ResponseAPDU verifyPIN(String pin) throws CardException {
CommandAPDU verifyPIN = new CommandAPDU(0x80, WalletApplet.INS_VERIFY_PIN, 0, 0, secureChannel.encryptAPDU(pin.getBytes())); CommandAPDU verifyPIN = new CommandAPDU(0x80, WalletApplet.INS_VERIFY_PIN, 0, 0, secureChannel.encryptAPDU(pin.getBytes()));
return apduChannel.transmit(verifyPIN); return apduChannel.transmit(verifyPIN);

View File

@ -12,7 +12,6 @@ import org.web3j.crypto.*;
import org.web3j.protocol.Web3j; import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameterName; import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.request.RawTransaction; 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.core.methods.response.EthSendTransaction;
import org.web3j.protocol.http.HttpService; import org.web3j.protocol.http.HttpService;
import org.web3j.tx.Transfer; import org.web3j.tx.Transfer;
@ -106,6 +105,36 @@ public class WalletAppletTest {
assertEquals(SecureChannel.SC_SECRET_LENGTH, response.getData().length); 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 @Test
@DisplayName("VERIFY PIN command") @DisplayName("VERIFY PIN command")
void verifyPinTest() throws CardException { void verifyPinTest() throws CardException {
@ -442,8 +471,6 @@ public class WalletAppletTest {
} }
assertFalse(ethSendTransaction.hasError()); assertFalse(ethSendTransaction.hasError());
EthGetTransactionReceipt receipt = web3j.ethGetTransactionReceipt(ethSendTransaction.getTransactionHash()).send();
} }
private KeyPairGenerator keypairGenerator() throws Exception { private KeyPairGenerator keypairGenerator() throws Exception {
@ -481,7 +508,7 @@ public class WalletAppletTest {
cmdSet.openSecureChannel(); cmdSet.openSecureChannel();
} }
public Sign.SignatureData signMessage(byte[] message) throws Exception { private Sign.SignatureData signMessage(byte[] message) throws Exception {
byte[] messageHash = Hash.sha3(message); byte[] messageHash = Hash.sha3(message);
ResponseAPDU response = cmdSet.sign(messageHash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true); ResponseAPDU response = cmdSet.sign(messageHash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true);