add ability to export current public key

This commit is contained in:
Michele Balistreri 2018-01-19 12:04:55 +03:00
parent 404aef15a0
commit e15da6a7e0
4 changed files with 97 additions and 34 deletions

View File

@ -301,20 +301,31 @@ will work even if no PIN authentication has been performed. An empty sequence me
* CLA = 0x80
* INS = 0xC2
* P1 = 0x01 (Whisper key)
* P2 = 0x00
* Response SW = 0x9000 on success, 0x6A86 if P1 is wrong
* P1 = key path index
* P2 = export options
* Response SW = 0x9000 on success, 0x6A86 if P1 or P2 are wrong
* Response Data = key pair template
* Preconditions: Secure Channel must be opened, user PIN must be verified, the current key path must match the one of
the key selected through P1
P1:
0x00 = Any key
0x01 = Whisper key (m/1/1)
P2:
0x00 = private and public key
0x01 = public key only
Response Data format:
- Tag 0xA1 = keypair template
- Tag 0x80 = ECC public key component
- Tag 0x81 = ECC private key component
- Tag 0x81 = ECC private key component (if P2=0x00)
This command exports the current public and private key if and only if the current key path matches the one of the key
selected by P1. P1 is only an index, the actual key path is stored immutably in the applet itself. At the moment only
the Whisper key (P1=0x01) can be exported and its key path is m/1/1. Other key paths could be added in the future, but
the last should remain as short as possible because of the security implications of revealing private keys to a possibly
compromised device. The current chain code is never exported to make it impossible to further derive keys off-card.
The special index 0x00 indicates any path, which means the current key will always be exported regardless of its actual
path. This works however only in combination with P2=0x01, so only the public key will be exported.

View File

@ -53,8 +53,12 @@ public class WalletApplet extends Applet {
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 EXPORT_KEY_P1_ANY = 0x00;
static final byte EXPORT_KEY_P1_WHISPER = 0x01;
static final byte EXPORT_KEY_P2_PRIVATE_AND_PUBLIC = 0x00;
static final byte EXPORT_KEY_P2_PUBLIC_ONLY = 0x01;
static final byte TLV_SIGNATURE_TEMPLATE = (byte) 0xA0;
static final byte TLV_KEY_TEMPLATE = (byte) 0xA1;
@ -863,8 +867,28 @@ public class WalletApplet extends Applet {
}
byte[] toExport;
boolean publicOnly;
switch (apduBuffer[ISO7816.OFFSET_P2]) {
case EXPORT_KEY_P2_PRIVATE_AND_PUBLIC:
publicOnly = false;
break;
case EXPORT_KEY_P2_PUBLIC_ONLY:
publicOnly = true;
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
return;
}
switch (apduBuffer[ISO7816.OFFSET_P1]) {
case EXPORT_KEY_P1_ANY:
if (!publicOnly) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
toExport = null;
break;
case EXPORT_KEY_P1_WHISPER:
toExport = WHISPER_KEY_PATH;
break;
@ -873,7 +897,7 @@ public class WalletApplet extends Applet {
return;
}
if (!((keyPathLen == toExport.length) && (Util.arrayCompare(keyPath, (short) 0, toExport, (short) 0, keyPathLen) == 0))) {
if (!((toExport == null) || ((keyPathLen == toExport.length) && (Util.arrayCompare(keyPath, (short) 0, toExport, (short) 0, keyPathLen) == 0)))) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
@ -886,11 +910,16 @@ public class WalletApplet extends Applet {
short len = publicKey.getW(apduBuffer, off);
apduBuffer[(short)(off - 1)] = (byte) len;
off += len;
apduBuffer[off++] = TLV_PRIV_KEY;
off++;
len = privateKey.getS(apduBuffer, off);
apduBuffer[(short)(off - 1)] = (byte) len;
len += (off - SecureChannel.SC_OUT_OFFSET);
if (!publicOnly) {
apduBuffer[off++] = TLV_PRIV_KEY;
off++;
len = privateKey.getS(apduBuffer, off);
apduBuffer[(short) (off - 1)] = (byte) len;
off += len;
}
len = (short) (off - SecureChannel.SC_OUT_OFFSET);
apduBuffer[(SecureChannel.SC_OUT_OFFSET + 1)] = (byte) (len - 2);
secureChannel.respond(apdu, (short) len, ISO7816.SW_NO_ERROR);

View File

@ -422,11 +422,13 @@ public class WalletAppletCommandSet {
* Sends an EXPORT KEY APDU. The keyPathIndex is used as P1. Valid values are defined in the applet itself
*
* @param keyPathIndex the P1 parameter
* @param publicOnly the P2 parameter
* @return the raw card response
* @throws CardException communication error
*/
public ResponseAPDU exportKey(byte keyPathIndex) throws CardException {
CommandAPDU exportKey = secureChannel.protectedCommand(0x80, WalletApplet.INS_EXPORT_KEY, keyPathIndex, 0x00, new byte[0]);
public ResponseAPDU exportKey(byte keyPathIndex, boolean publicOnly) throws CardException {
byte p2 = publicOnly ? WalletApplet.EXPORT_KEY_P2_PUBLIC_ONLY : WalletApplet.EXPORT_KEY_P2_PRIVATE_AND_PUBLIC;
CommandAPDU exportKey = secureChannel.protectedCommand(0x80, WalletApplet.INS_EXPORT_KEY, keyPathIndex, p2, new byte[0]);
return secureChannel.transmit(apduChannel, exportKey);
}
}

View File

@ -804,13 +804,13 @@ public class WalletAppletTest {
new Random().nextBytes(chainCode);
// Security condition violation: SecureChannel not open
ResponseAPDU response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER);
ResponseAPDU response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false);
assertEquals(0x6985, response.getSW());
cmdSet.autoOpenSecureChannel();
// Security condition violation: PIN not verified
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER);
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false);
assertEquals(0x6985, response.getSW());
response = cmdSet.verifyPIN("000000");
@ -819,36 +819,51 @@ public class WalletAppletTest {
assertEquals(0x9000, response.getSW());
// Security condition violation: current key is not Whisper key
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER);
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false);
assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, true, true, false);
assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(response.getData()), false, true, true);
assertEquals(0x9000, response.getSW());
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER);
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false);
assertEquals(0x6985, response.getSW());
// Export current public key (wrong P2)
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_ANY, false);
assertEquals(0x6985, response.getSW());
// Export current public key
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_ANY, true);
assertEquals(0x9000, response.getSW());
byte[] keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 1 }, true);
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, false, true, false);
assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(response.getData()), false, true, true);
assertEquals(0x9000, response.getSW());
// Wrong P1
response = cmdSet.exportKey((byte) 0);
assertEquals(0x6a86, response.getSW());
response = cmdSet.exportKey((byte) 2);
response = cmdSet.exportKey((byte) 2, false);
assertEquals(0x6a86, response.getSW());
// Correct
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER);
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false);
assertEquals(0x9000, response.getSW());
byte[] keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 1, 1 });
keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 1, 1 }, false);
// Correct public only
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, true);
assertEquals(0x9000, response.getSW());
keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 1, 1 }, true);
// Reset
response = cmdSet.deriveKey(new byte[] {}, true, false, false);
assertEquals(0x9000, response.getSW());
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER);
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false);
assertEquals(0x6985, response.getSW());
}
@ -1096,22 +1111,28 @@ public class WalletAppletTest {
}
}
private void verifyExportedKey(byte[] keyTemplate, KeyPair keyPair, byte[] chainCode, int[] path) {
private void verifyExportedKey(byte[] keyTemplate, KeyPair keyPair, byte[] chainCode, int[] path, boolean publicOnly) {
ECKey key = deriveKey(keyPair, chainCode, path).decompress();
assertEquals(WalletApplet.TLV_KEY_TEMPLATE, keyTemplate[0]);
assertEquals(WalletApplet.TLV_PUB_KEY, keyTemplate[2]);
byte[] pubKey = Arrays.copyOfRange(keyTemplate, 4, 4 + keyTemplate[3]);
assertEquals(WalletApplet.TLV_PRIV_KEY, keyTemplate[4 + pubKey.length]);
byte[] privateKey = Arrays.copyOfRange(keyTemplate, 6 + pubKey.length, 6 + pubKey.length + keyTemplate[5 + pubKey.length]);
byte[] tPrivKey = key.getPrivKey().toByteArray();
if (tPrivKey[0] == 0x00) {
tPrivKey = Arrays.copyOfRange(tPrivKey, 1, tPrivKey.length);
}
assertArrayEquals(key.getPubKey(), pubKey);
assertArrayEquals(tPrivKey, privateKey);
if (publicOnly) {
assertEquals(pubKey.length + 2, keyTemplate[1]);
assertEquals(pubKey.length + 4, keyTemplate.length);
} else {
assertEquals(WalletApplet.TLV_PRIV_KEY, keyTemplate[4 + pubKey.length]);
byte[] privateKey = Arrays.copyOfRange(keyTemplate, 6 + pubKey.length, 6 + pubKey.length + keyTemplate[5 + pubKey.length]);
byte[] tPrivKey = key.getPrivKey().toByteArray();
if (tPrivKey[0] == 0x00) {
tPrivKey = Arrays.copyOfRange(tPrivKey, 1, tPrivKey.length);
}
assertArrayEquals(tPrivKey, privateKey);
}
}
private DeterministicKey deriveKey(KeyPair keyPair, byte[] chainCode, int[] path) {