implemented EXPORT KEY changes

This commit is contained in:
Michele Balistreri 2018-09-18 11:26:24 +03:00
parent 6af167612e
commit fde5d12850
3 changed files with 26 additions and 37 deletions

View File

@ -330,7 +330,7 @@ will work even if no PIN authentication has been performed. An empty sequence me
P1:
0x00 = Any key
0x01 = Whisper key (m/1/1)
0x01 = Non-wallet key
P2:
0x00 = private and public key
@ -341,11 +341,10 @@ Response Data format:
- Tag 0x80 = ECC public 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.
This command exports the current public and private key if and only if the current key path matches the conditions
dictated by the given P1 parameter. This currently allows exporting any key whose last path component has a key index
with the two most significant bits set (that is, matches the 0xc0000000 mask). No key in this range should be used for
wallet accounts, even as intermediate path component. These keys are meant for client-specific use such as the Whisper key.
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

@ -59,8 +59,7 @@ public class WalletApplet extends Applet {
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_P1_DATABASE = 0x02;
static final byte EXPORT_KEY_P1_HIGH = 0x01;
static final byte EXPORT_KEY_P2_PRIVATE_AND_PUBLIC = 0x00;
static final byte EXPORT_KEY_P2_PUBLIC_ONLY = 0x01;
@ -83,8 +82,7 @@ public class WalletApplet extends Applet {
static final byte TLV_UID = (byte) 0x8F;
private static final byte[] ASSISTED_DERIVATION_HASH = {(byte) 0xAA, (byte) 0x2D, (byte) 0xA9, (byte) 0x9D, (byte) 0x91, (byte) 0x8C, (byte) 0x7D, (byte) 0x95, (byte) 0xB8, (byte) 0x96, (byte) 0x89, (byte) 0x87, (byte) 0x3E, (byte) 0xAA, (byte) 0x37, (byte) 0x67, (byte) 0x25, (byte) 0x0C, (byte) 0xFF, (byte) 0x50, (byte) 0x13, (byte) 0x9A, (byte) 0x2F, (byte) 0x87, (byte) 0xBB, (byte) 0x4F, (byte) 0xCA, (byte) 0xB4, (byte) 0xAE, (byte) 0xC3, (byte) 0xE8, (byte) 0x90};
private static final byte[] WHISPER_KEY_PATH = {(byte) 0x80, 0x00, 0x00, 0x2c, (byte) 0x80, 0x00, 0x00, 0x3c, (byte) 0x80, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0xC0, 0x00, 0x00, 0x00};
private static final byte[] DATABASE_KEY_PATH = {(byte) 0x80, 0x00, 0x00, 0x2c, (byte) 0x80, 0x00, 0x00, 0x3c, (byte) 0x80, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0xC0, 0x00, 0x00, 0x01};
private static final byte EXPORT_KEY_HIGH_MASK = (byte) 0xc0;
private OwnerPIN pin;
private OwnerPIN puk;
@ -930,7 +928,6 @@ public class WalletApplet extends Applet {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
byte[] toExport;
boolean publicOnly;
switch (apduBuffer[ISO7816.OFFSET_P2]) {
@ -950,24 +947,17 @@ public class WalletApplet extends Applet {
if (!publicOnly) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
toExport = null;
break;
case EXPORT_KEY_P1_WHISPER:
toExport = WHISPER_KEY_PATH;
break;
case EXPORT_KEY_P1_DATABASE:
toExport = DATABASE_KEY_PATH;
case EXPORT_KEY_P1_HIGH:
if (keyPathLen < 4 || ((((byte)(keyPath[(byte)(keyPathLen - 4)] & EXPORT_KEY_HIGH_MASK)) != EXPORT_KEY_HIGH_MASK))){
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
return;
}
if (!((toExport == null) || ((keyPathLen == toExport.length) && (Util.arrayCompare(keyPath, (short) 0, toExport, (short) 0, keyPathLen) == 0)))) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
short off = SecureChannel.SC_OUT_OFFSET;
apduBuffer[off++] = TLV_KEY_TEMPLATE;
@ -989,7 +979,7 @@ public class WalletApplet extends Applet {
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);
secureChannel.respond(apdu, len, ISO7816.SW_NO_ERROR);
}
/**

View File

@ -825,13 +825,13 @@ public class WalletAppletTest {
new Random().nextBytes(chainCode);
// Security condition violation: SecureChannel not open
ResponseAPDU response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false);
ResponseAPDU response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_HIGH, false);
assertEquals(0x6985, response.getSW());
cmdSet.autoOpenSecureChannel();
// Security condition violation: PIN not verified
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false);
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_HIGH, false);
assertEquals(0x6985, response.getSW());
response = cmdSet.verifyPIN("000000");
@ -839,33 +839,33 @@ public class WalletAppletTest {
response = cmdSet.loadKey(keyPair, false, chainCode);
assertEquals(0x9000, response.getSW());
// Security condition violation: current key is not Whisper key
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false);
// Security condition violation: current key is not exportable
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_HIGH, false);
assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] { (byte) 0x80, 0x00, 0x00, 0x2c}, WalletApplet.DERIVE_P1_SOURCE_MASTER, true, false);
assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(response.getData()), WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, true);
assertEquals(0x9000, response.getSW());
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false);
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_HIGH, false);
assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] { (byte) 0x80, 0x00, 0x00, 0x3c}, WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, false);
assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(response.getData()), WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, true);
assertEquals(0x9000, response.getSW());
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false);
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_HIGH, false);
assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] { (byte) 0x80, 0x00, 0x00, 0x00}, WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, false);
assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(response.getData()), WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, true);
assertEquals(0x9000, response.getSW());
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false);
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_HIGH, false);
assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] { (byte) 0x00, 0x00, 0x00, 0x00}, WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, false);
assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(response.getData()), WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, true);
assertEquals(0x9000, response.getSW());
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false);
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_HIGH, false);
assertEquals(0x6985, response.getSW());
@ -879,7 +879,7 @@ public class WalletAppletTest {
byte[] keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002c, 0x8000003c, 0x80000000, 0x00000000 }, true);
response = cmdSet.deriveKey(new byte[] {(byte) 0xC0, 0x00, 0x00, 0x00}, WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, false);
response = cmdSet.deriveKey(new byte[] {(byte) 0xC0, 0x00, 0x00, 0x01}, WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, false);
assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(response.getData()), WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, true);
assertEquals(0x9000, response.getSW());
@ -889,21 +889,21 @@ public class WalletAppletTest {
assertEquals(0x6a86, response.getSW());
// Correct
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false);
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_HIGH, false);
assertEquals(0x9000, response.getSW());
keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002c, 0x8000003c, 0x80000000, 0x00000000, 0xC0000000 }, false);
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002c, 0x8000003c, 0x80000000, 0x00000000, 0xC0000001 }, false);
// Correct public only
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, true);
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_HIGH, true);
assertEquals(0x9000, response.getSW());
keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002c, 0x8000003c, 0x80000000, 0x00000000, 0xC0000000 }, true);
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002c, 0x8000003c, 0x80000000, 0x00000000, 0xC0000001 }, true);
// Reset
response = cmdSet.deriveKey(new byte[] {}, WalletApplet.DERIVE_P1_SOURCE_MASTER, false, false);
assertEquals(0x9000, response.getSW());
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false);
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_HIGH, false);
assertEquals(0x6985, response.getSW());
}