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: P1:
0x00 = Any key 0x00 = Any key
0x01 = Whisper key (m/1/1) 0x01 = Non-wallet key
P2: P2:
0x00 = private and public key 0x00 = private and public key
@ -341,11 +341,10 @@ Response Data format:
- Tag 0x80 = ECC public key component - Tag 0x80 = ECC public key component
- Tag 0x81 = ECC private key component (if P2=0x00) - 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 This command exports the current public and private key if and only if the current key path matches the conditions
selected by P1. P1 is only an index, the actual key path is stored immutably in the applet itself. At the moment only dictated by the given P1 parameter. This currently allows exporting any key whose last path component has a key index
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 with the two most significant bits set (that is, matches the 0xc0000000 mask). No key in this range should be used for
the last should remain as short as possible because of the security implications of revealing private keys to a possibly wallet accounts, even as intermediate path component. These keys are meant for client-specific use such as the Whisper key.
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 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. 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 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_ANY = 0x00;
static final byte EXPORT_KEY_P1_WHISPER = 0x01; static final byte EXPORT_KEY_P1_HIGH = 0x01;
static final byte EXPORT_KEY_P1_DATABASE = 0x02;
static final byte EXPORT_KEY_P2_PRIVATE_AND_PUBLIC = 0x00; static final byte EXPORT_KEY_P2_PRIVATE_AND_PUBLIC = 0x00;
static final byte EXPORT_KEY_P2_PUBLIC_ONLY = 0x01; 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; 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[] 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 EXPORT_KEY_HIGH_MASK = (byte) 0xc0;
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 OwnerPIN pin; private OwnerPIN pin;
private OwnerPIN puk; private OwnerPIN puk;
@ -930,7 +928,6 @@ public class WalletApplet extends Applet {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
} }
byte[] toExport;
boolean publicOnly; boolean publicOnly;
switch (apduBuffer[ISO7816.OFFSET_P2]) { switch (apduBuffer[ISO7816.OFFSET_P2]) {
@ -950,24 +947,17 @@ public class WalletApplet extends Applet {
if (!publicOnly) { if (!publicOnly) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
} }
toExport = null;
break; break;
case EXPORT_KEY_P1_WHISPER: case EXPORT_KEY_P1_HIGH:
toExport = WHISPER_KEY_PATH; if (keyPathLen < 4 || ((((byte)(keyPath[(byte)(keyPathLen - 4)] & EXPORT_KEY_HIGH_MASK)) != EXPORT_KEY_HIGH_MASK))){
break; ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
case EXPORT_KEY_P1_DATABASE: }
toExport = DATABASE_KEY_PATH;
break; break;
default: default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
return; 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; short off = SecureChannel.SC_OUT_OFFSET;
apduBuffer[off++] = TLV_KEY_TEMPLATE; apduBuffer[off++] = TLV_KEY_TEMPLATE;
@ -989,7 +979,7 @@ public class WalletApplet extends Applet {
len = (short) (off - SecureChannel.SC_OUT_OFFSET); len = (short) (off - SecureChannel.SC_OUT_OFFSET);
apduBuffer[(SecureChannel.SC_OUT_OFFSET + 1)] = (byte) (len - 2); 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); new Random().nextBytes(chainCode);
// Security condition violation: SecureChannel not open // 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()); assertEquals(0x6985, response.getSW());
cmdSet.autoOpenSecureChannel(); cmdSet.autoOpenSecureChannel();
// Security condition violation: PIN not verified // 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()); assertEquals(0x6985, response.getSW());
response = cmdSet.verifyPIN("000000"); response = cmdSet.verifyPIN("000000");
@ -839,33 +839,33 @@ public class WalletAppletTest {
response = cmdSet.loadKey(keyPair, false, chainCode); response = cmdSet.loadKey(keyPair, false, chainCode);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
// Security condition violation: current key is not Whisper key // Security condition violation: current key is not exportable
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false); response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_HIGH, false);
assertEquals(0x6985, response.getSW()); assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] { (byte) 0x80, 0x00, 0x00, 0x2c}, WalletApplet.DERIVE_P1_SOURCE_MASTER, true, false); response = cmdSet.deriveKey(new byte[] { (byte) 0x80, 0x00, 0x00, 0x2c}, WalletApplet.DERIVE_P1_SOURCE_MASTER, true, false);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(response.getData()), WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, true); response = cmdSet.deriveKey(derivePublicKey(response.getData()), WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, true);
assertEquals(0x9000, response.getSW()); 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()); assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] { (byte) 0x80, 0x00, 0x00, 0x3c}, WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, false); response = cmdSet.deriveKey(new byte[] { (byte) 0x80, 0x00, 0x00, 0x3c}, WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, false);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(response.getData()), WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, true); response = cmdSet.deriveKey(derivePublicKey(response.getData()), WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, true);
assertEquals(0x9000, response.getSW()); 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()); assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] { (byte) 0x80, 0x00, 0x00, 0x00}, WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, false); response = cmdSet.deriveKey(new byte[] { (byte) 0x80, 0x00, 0x00, 0x00}, WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, false);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(response.getData()), WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, true); response = cmdSet.deriveKey(derivePublicKey(response.getData()), WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, true);
assertEquals(0x9000, response.getSW()); 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()); assertEquals(0x6985, response.getSW());
response = cmdSet.deriveKey(new byte[] { (byte) 0x00, 0x00, 0x00, 0x00}, WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, false); response = cmdSet.deriveKey(new byte[] { (byte) 0x00, 0x00, 0x00, 0x00}, WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, false);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(response.getData()), WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, true); response = cmdSet.deriveKey(derivePublicKey(response.getData()), WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, true);
assertEquals(0x9000, response.getSW()); 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()); assertEquals(0x6985, response.getSW());
@ -879,7 +879,7 @@ public class WalletAppletTest {
byte[] keyTemplate = response.getData(); byte[] keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002c, 0x8000003c, 0x80000000, 0x00000000 }, true); 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()); assertEquals(0x9000, response.getSW());
response = cmdSet.deriveKey(derivePublicKey(response.getData()), WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, true); response = cmdSet.deriveKey(derivePublicKey(response.getData()), WalletApplet.DERIVE_P1_SOURCE_CURRENT, true, true);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
@ -889,21 +889,21 @@ public class WalletAppletTest {
assertEquals(0x6a86, response.getSW()); assertEquals(0x6a86, response.getSW());
// Correct // Correct
response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_WHISPER, false); response = cmdSet.exportKey(WalletApplet.EXPORT_KEY_P1_HIGH, false);
assertEquals(0x9000, response.getSW()); assertEquals(0x9000, response.getSW());
keyTemplate = response.getData(); 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 // 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()); assertEquals(0x9000, response.getSW());
keyTemplate = response.getData(); 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 // Reset
response = cmdSet.deriveKey(new byte[] {}, WalletApplet.DERIVE_P1_SOURCE_MASTER, false, false); response = cmdSet.deriveKey(new byte[] {}, WalletApplet.DERIVE_P1_SOURCE_MASTER, false, false);
assertEquals(0x9000, response.getSW()); 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()); assertEquals(0x6985, response.getSW());
} }