add ability to export current public key
This commit is contained in:
parent
404aef15a0
commit
e15da6a7e0
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue