diff --git a/build.gradle b/build.gradle index 413d147..b7446c7 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ dependencies { testCompile(files("../jcardsim/jcardsim-3.0.5-SNAPSHOT.jar")) testCompile('org.web3j:core:2.3.1') testCompile('org.bitcoinj:bitcoinj-core:0.14.5') - testCompile('com.github.status-im.status-keycard-java:desktop:15a61e1') + testCompile('com.github.status-im.status-keycard-java:desktop:3.1.2') testCompile('org.bouncycastle:bcprov-jdk15on:1.65') testCompile("org.junit.jupiter:junit-jupiter-api:5.1.1") testRuntime("org.junit.jupiter:junit-jupiter-engine:5.1.1") diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 44786a5..3e21886 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -4,5 +4,5 @@ repositories { } dependencies { - compile 'com.github.status-im.status-keycard-java:desktop:15a61e1' + compile 'com.github.status-im.status-keycard-java:desktop:3.1.2' } \ No newline at end of file diff --git a/src/main/java/im/status/keycard/KeycardApplet.java b/src/main/java/im/status/keycard/KeycardApplet.java index aee521e..798e403 100644 --- a/src/main/java/im/status/keycard/KeycardApplet.java +++ b/src/main/java/im/status/keycard/KeycardApplet.java @@ -13,6 +13,7 @@ public class KeycardApplet extends Applet { static final byte INS_GET_STATUS = (byte) 0xF2; static final byte INS_INIT = (byte) 0xFE; + static final byte INS_FACTORY_RESET = (byte) 0xFD; static final byte INS_VERIFY_PIN = (byte) 0x20; static final byte INS_CHANGE_PIN = (byte) 0x21; static final byte INS_UNBLOCK_PIN = (byte) 0x22; @@ -85,6 +86,9 @@ public class KeycardApplet extends Applet { static final byte STORE_DATA_P1_NDEF = 0x01; static final byte STORE_DATA_P1_CASH = 0x02; + static final byte FACTORY_RESET_P1_MAGIC = (byte) 0xAA; + static final byte FACTORY_RESET_P2_MAGIC = 0x55; + static final byte TLV_SIGNATURE_TEMPLATE = (byte) 0xA0; static final byte TLV_KEY_TEMPLATE = (byte) 0xA1; @@ -105,8 +109,9 @@ public class KeycardApplet extends Applet { static final byte CAPABILITY_KEY_MANAGEMENT = (byte) 0x02; static final byte CAPABILITY_CREDENTIALS_MANAGEMENT = (byte) 0x04; static final byte CAPABILITY_NDEF = (byte) 0x08; + static final byte CAPABILITY_FACTORY_RESET = (byte) 0x10; - static final byte APPLICATION_CAPABILITIES = (byte)(CAPABILITY_SECURE_CHANNEL | CAPABILITY_KEY_MANAGEMENT | CAPABILITY_CREDENTIALS_MANAGEMENT | CAPABILITY_NDEF); + static final byte APPLICATION_CAPABILITIES = (byte)(CAPABILITY_SECURE_CHANNEL | CAPABILITY_KEY_MANAGEMENT | CAPABILITY_CREDENTIALS_MANAGEMENT | CAPABILITY_NDEF | CAPABILITY_FACTORY_RESET); static final byte[] EIP_1581_PREFIX = { (byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D}; @@ -282,6 +287,9 @@ public void process(APDU apdu) throws ISOException { case INS_STORE_DATA: storeData(apdu); break; + case INS_FACTORY_RESET: + factoryReset(apdu); + return; default: ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); break; @@ -1008,6 +1016,26 @@ private short logicrShift(short v, short amount) { return (short) ((short)((short) 0x4000 >>> (short) (amount - 1)) | tmp); } + /** + * Clear all keys and erases the key UID. + */ + private void clearKeys() { + keyPathLen = 0; + pinlessPathLen = 0; + tmpPathLen = 0; + isExtended = false; + masterPrivate.clearKey(); + masterPublic.clearKey(); + resetCurveParameters(); + Util.arrayFillNonAtomic(masterChainCode, (short) 0, (short) masterChainCode.length, (byte) 0); + Util.arrayFillNonAtomic(altChainCode, (short) 0, (short) altChainCode.length, (byte) 0); + Util.arrayFillNonAtomic(keyPath, (short) 0, (short) keyPath.length, (byte) 0); + Util.arrayFillNonAtomic(pinlessPath, (short) 0, (short) pinlessPath.length, (byte) 0); + Util.arrayFillNonAtomic(tmpPath, (short) 0, (short) tmpPath.length, (byte) 0); + Util.arrayFillNonAtomic(derivationOutput, (short) 0, (short) derivationOutput.length, (byte) 0); + Util.arrayFillNonAtomic(keyUID, (short) 0, (short) keyUID.length, (byte) 0); + } + /** * Processes the REMOVE KEY command. Removes the master key and all derived keys. Secure Channel and PIN * authentication are required. @@ -1022,16 +1050,28 @@ private void removeKey(APDU apdu) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } - keyPathLen = 0; - pinlessPathLen = 0; - isExtended = false; - masterPrivate.clearKey(); - masterPublic.clearKey(); - resetCurveParameters(); - Util.arrayFillNonAtomic(masterChainCode, (short) 0, (short) masterChainCode.length, (byte) 0); - Util.arrayFillNonAtomic(altChainCode, (short) 0, (short) altChainCode.length, (byte) 0); - Util.arrayFillNonAtomic(keyPath, (short) 0, (short) keyPath.length, (byte) 0); - Util.arrayFillNonAtomic(pinlessPath, (short) 0, (short) pinlessPath.length, (byte) 0); + clearKeys(); + } + + private void factoryReset(APDU apdu) { + byte[] apduBuffer = apdu.getBuffer(); + + if ((apduBuffer[OFFSET_P1] != FACTORY_RESET_P1_MAGIC) || (apduBuffer[ISO7816.OFFSET_P2] != FACTORY_RESET_P2_MAGIC)) { + ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); + } + + clearKeys(); + pin = null; + mainPIN = null; + altPIN = null; + puk = null; + secureChannel = null; + crypto.random.generateData(uid, (short) 0, UID_LENGTH); + Util.arrayFillNonAtomic(data, (short) 0, (short) data.length, (byte) 0); + + if (JCSystem.isObjectDeletionSupported()) { + JCSystem.requestObjectDeletion(); + } } /** diff --git a/src/test/java/im/status/keycard/KeycardTest.java b/src/test/java/im/status/keycard/KeycardTest.java index 9759318..2e2a84e 100644 --- a/src/test/java/im/status/keycard/KeycardTest.java +++ b/src/test/java/im/status/keycard/KeycardTest.java @@ -128,6 +128,10 @@ private static void initCapabilities(ApplicationInfo info) { capabilities.add("ndef"); } + if (info.hasFactoryResetCapability()) { + capabilities.add("factoryReset"); + } + CapabilityCondition.availableCapabilities = capabilities; } @@ -210,6 +214,11 @@ public void onDisconnected() { usbManager.start(); } + private static void initCard(KeycardCommandSet cmdSet) throws Exception { + assertEquals(0x9000, cmdSet.init("000000", "024680", "012345678901", sharedSecret, (byte) 3, (byte) 5).getSw()); + cmdSet.select().checkOK(); + } + private static void initIfNeeded() throws Exception { KeyPair identKeyPair = Certificate.generateIdentKeyPair(); Certificate cert = Certificate.createCertificate(caKeyPair, identKeyPair); @@ -225,8 +234,7 @@ private static void initIfNeeded() throws Exception { sharedSecret = cmdSet.pairingPasswordToSecret(System.getProperty("im.status.keycard.test.pairing", "KeycardDefaultPairing")); if (!cmdSet.getApplicationInfo().isInitializedCard()) { - assertEquals(0x9000, cmdSet.init("000000", "024680", "012345678901", sharedSecret, (byte) 3, (byte) 5).getSw()); - cmdSet.select().checkOK(); + initCard(cmdSet); initCapabilities(cmdSet.getApplicationInfo()); } } @@ -932,6 +940,46 @@ void removeKeyTest() throws Exception { assertEquals(0, info.getKeyUID().length); } + @Test + @DisplayName("FACTORY RESET command") + @Capabilities("factoryReset") + void factoryResetTest() throws Exception { + KeyPairGenerator g = keypairGenerator(); + KeyPair keyPair = g.generateKeyPair(); + + // Invalid P1 P2 + APDUResponse response = sdkChannel.send(new APDUCommand(0x80, KeycardApplet.INS_FACTORY_RESET, 0, 0, new byte[0])); + assertEquals(0x6a86, response.getSw()); + + // Good case + response = cmdSet.factoryReset(); + assertEquals(0x9000, response.getSw()); + + response = cmdSet.getStatus(KeycardCommandSet.GET_STATUS_P1_KEY_PATH); + assertEquals(0x6d00, response.getSw()); + + response = cmdSet.select(); + assertEquals(0x9000, response.getSw()); + assertFalse(cmdSet.getApplicationInfo().isInitializedCard()); + + initCard(cmdSet); + + response = cmdSet.select(); + assertEquals(0x9000, response.getSw()); + + if (cmdSet.getApplicationInfo().hasSecureChannelCapability()) { + cmdSet.autoPair(sharedSecret); + cmdSet.autoOpenSecureChannel(); + } + + if (cmdSet.getApplicationInfo().hasCredentialsManagementCapability()) { + response = cmdSet.verifyPIN("000000"); + assertEquals(0x9000, response.getSw()); + } + + assertFalse(cmdSet.getKeyInitializationStatus()); + } + @Test @DisplayName("GENERATE KEY command") @Capabilities("keyManagement")