diff --git a/APPLICATION.MD b/APPLICATION.MD index 228e50b..cac439e 100644 --- a/APPLICATION.MD +++ b/APPLICATION.MD @@ -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. diff --git a/src/main/java/im/status/wallet/WalletApplet.java b/src/main/java/im/status/wallet/WalletApplet.java index 4022e6f..c52a1d6 100644 --- a/src/main/java/im/status/wallet/WalletApplet.java +++ b/src/main/java/im/status/wallet/WalletApplet.java @@ -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); } /** diff --git a/src/test/java/im/status/wallet/WalletAppletTest.java b/src/test/java/im/status/wallet/WalletAppletTest.java index 3cacc36..8bbe665 100644 --- a/src/test/java/im/status/wallet/WalletAppletTest.java +++ b/src/test/java/im/status/wallet/WalletAppletTest.java @@ -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()); }