add remove key command

This commit is contained in:
Michele Balistreri 2018-10-01 10:58:44 +02:00
parent 3c5fd3bfca
commit 6a0ca5e260
4 changed files with 116 additions and 12 deletions

View File

@ -104,8 +104,8 @@ if P1 = 0x00:
- Tag 0xA3 = Application Status Template
- Tag 0x02 = PIN retry count (1 byte)
- Tag 0x02 = PUK retry count (1 byte)
- Tag 0x01 = 0 if key is not initialized, 1 otherwise
- Tag 0x01 = 1 if public key derivation is supported, 0 otherwise
- Tag 0x01 = 0xff if key is initialized, 0 otherwise
- Tag 0x01 = 0xff if public key derivation is supported, 0 otherwise
if P1 = 0x01
- a sequence of 32-bit numbers indicating the current key path. Empty if master key is selected.
@ -255,6 +255,18 @@ Used to generate a mnemonic according to the algorithm specified in [BIP39](http
The returned data is a list of 16-byte integers which should be used as indexes in a wordlist to generate the
human-readable mnemonic. Each integer can have a value from 0 to 2047.
### REMOVE KEY
* CLA = 0x80
* INS = 0xD3
* P1 = 0x00
* P2 = 0x00
* Response SW = 0x9000 on success.
* Preconditions: Secure Channel must be opened, user PIN must be verified
Removes the key from the card, bringing it back to an uninitialized state. No signing operation is possible after this
command until a new LOAD KEY command is performed.
### SIGN
* CLA = 0x80

View File

@ -16,6 +16,7 @@ public class WalletApplet extends Applet {
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_REMOVE_KEY = (byte) 0xD3;
static final byte INS_SIGN = (byte) 0xC0;
static final byte INS_SET_PINLESS_PATH = (byte) 0xC1;
static final byte INS_EXPORT_KEY = (byte) 0xC2;
@ -160,15 +161,7 @@ public class WalletApplet extends Applet {
keyPath = new byte[KEY_PATH_MAX_DEPTH * 4];
pinlessPath = new byte[KEY_PATH_MAX_DEPTH * 4];
secp256k1.setCurveParameters(masterPublic);
secp256k1.setCurveParameters(masterPrivate);
secp256k1.setCurveParameters(parentPublicKey);
secp256k1.setCurveParameters(parentPrivateKey);
parentValid = false;
secp256k1.setCurveParameters(publicKey);
secp256k1.setCurveParameters(privateKey);
resetCurveParameters();
signature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false);
@ -239,6 +232,9 @@ public class WalletApplet extends Applet {
case INS_GENERATE_MNEMONIC:
generateMnemonic(apdu);
break;
case INS_REMOVE_KEY:
removeKey(apdu);
break;
case INS_SIGN:
sign(apdu);
break;
@ -832,6 +828,34 @@ public class WalletApplet extends Applet {
return (short) ((short)((short) 0x4000 >>> (short) (amount - 1)) | tmp);
}
private void removeKey(APDU apdu) {
byte[] apduBuffer = apdu.getBuffer();
secureChannel.preprocessAPDU(apduBuffer);
if (!pin.isValidated()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
keyPathLen = 0;
pinlessPathLen = 0;
parentValid = false;
isExtended = false;
signInProgress = false;
expectPublicKey = false;
privateKey.clearKey();
publicKey.clearKey();
masterPrivate.clearKey();
masterPublic.clearKey();
parentPrivateKey.clearKey();
parentPublicKey.clearKey();
resetCurveParameters();
Util.arrayFillNonAtomic(chainCode, (short) 0, (short) chainCode.length, (byte) 0);
Util.arrayFillNonAtomic(parentChainCode, (short) 0, (short) parentChainCode.length, (byte) 0);
Util.arrayFillNonAtomic(masterChainCode, (short) 0, (short) masterChainCode.length, (byte) 0);
Util.arrayFillNonAtomic(keyPath, (short) 0, (short) keyPath.length, (byte) 0);
Util.arrayFillNonAtomic(pinlessPath, (short) 0, (short) pinlessPath.length, (byte) 0);
}
/**
* Processes the SIGN command. Requires a secure channel to open and either the PIN to be verified or the PIN-less key
* path to be the current key path. This command supports signing data using SHA-256 with possible segmentation over
@ -927,7 +951,7 @@ public class WalletApplet extends Applet {
byte[] apduBuffer = apdu.getBuffer();
secureChannel.preprocessAPDU(apduBuffer);
if (!pin.isValidated()) {
if (!pin.isValidated() || !privateKey.isInitialized()) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
@ -1014,4 +1038,18 @@ public class WalletApplet extends Applet {
private boolean isPinless() {
return (pinlessPathLen > 0) && (pinlessPathLen == keyPathLen) && (Util.arrayCompare(keyPath, (short) 0, pinlessPath, (short) 0, keyPathLen) == 0);
}
/**
* Set curve parameters to cleared keys
*/
private void resetCurveParameters() {
secp256k1.setCurveParameters(masterPublic);
secp256k1.setCurveParameters(masterPrivate);
secp256k1.setCurveParameters(parentPublicKey);
secp256k1.setCurveParameters(parentPrivateKey);
secp256k1.setCurveParameters(publicKey);
secp256k1.setCurveParameters(privateKey);
}
}

View File

@ -138,6 +138,19 @@ public class WalletAppletCommandSet {
return data[data.length - 1] != 0x00;
}
/**
* Sends a GET STATUS APDU to retrieve the APPLICATION STATUS template and reads the byte indicating key initialization
* status
*
* @return whether public key derivation is supported or not
* @throws CardException communication error
*/
public boolean getKeyInitializationStatus() throws CardException {
ResponseAPDU resp = getStatus(WalletApplet.GET_STATUS_P1_APPLICATION);
byte[] data = resp.getData();
return data[data.length - 4] != 0x00;
}
/**
* Sends a VERIFY PIN APDU. The raw bytes of the given string are encrypted using the secure channel and used as APDU
* data.
@ -355,6 +368,17 @@ public class WalletAppletCommandSet {
return secureChannel.transmit(apduChannel, generateMnemonic);
}
/**
* Sends a REMOVE KEY APDU.
*
* @return the raw card response
* @throws CardException communication error
*/
public ResponseAPDU removeKey() throws CardException {
CommandAPDU removeKey = secureChannel.protectedCommand(0x80, WalletApplet.INS_REMOVE_KEY, 0, 0, new byte[0]);
return secureChannel.transmit(apduChannel, removeKey);
}
/**
* Sends a SIGN APDU. The dataType is P1 as defined in the applet. The isFirst and isLast arguments are used to form
* the P2 parameter. The data is the data to sign, or part of it. Only when sending the last block a signature is

View File

@ -550,6 +550,36 @@ public class WalletAppletTest {
assertMnemonic(24, response.getData());
}
@Test
@DisplayName("REMOVE KEY command")
void removeKeyTest() throws Exception {
KeyPairGenerator g = keypairGenerator();
KeyPair keyPair = g.generateKeyPair();
// Security condition violation: SecureChannel not open
ResponseAPDU response = cmdSet.removeKey();
assertEquals(0x6985, response.getSW());
cmdSet.autoOpenSecureChannel();
// Security condition violation: PIN not verified
response = cmdSet.removeKey();
assertEquals(0x6985, response.getSW());
response = cmdSet.verifyPIN("000000");
assertEquals(0x9000, response.getSW());
response = cmdSet.loadKey(keyPair);
assertEquals(0x9000, response.getSW());
assertTrue(cmdSet.getKeyInitializationStatus());
// Good case
response = cmdSet.removeKey();
assertEquals(0x9000, response.getSW());
assertFalse(cmdSet.getKeyInitializationStatus());
}
@Test
@DisplayName("DERIVE KEY command")
void deriveKeyTest() throws Exception {