From 90310b7036ef0dbb4d730f14a731390ee9a5e4c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Thu, 2 Nov 2017 18:52:11 +0100 Subject: [PATCH] Read life cycle management from historical bytes --- .../securitytoken/CardCapabilities.java | 44 ++++++++++++++++--- .../OpenPgpCommandApduFactory.java | 8 ++-- .../securitytoken/SecurityTokenUtilsTest.java | 15 +++++++ 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardCapabilities.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardCapabilities.java index dc8a65e6d..2b5fd6151 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardCapabilities.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardCapabilities.java @@ -20,24 +20,32 @@ package org.sufficientlysecure.keychain.securitytoken; import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException; import java.nio.ByteBuffer; +import java.util.Arrays; @SuppressWarnings("WeakerAccess") class CardCapabilities { private static final int MASK_CHAINING = 1 << 7; private static final int MASK_EXTENDED = 1 << 6; - private byte[] mCapabilityBytes; + private static final int STATUS_INDICATOR_NO_INFORMATION = 0x00; + private static final int STATUS_INDICATOR_INITIALISATION_STATE = 0x03; + private static final int STATUS_INDICATOR_OPERATIONAL_STATE = 0x05; + + private static final byte[] EXPECTED_PROCESSING_STATUS_BYTES = {(byte) 0x90, (byte) 0x00}; + + private byte[] historicalBytes; + private byte[] capabilityBytes; public CardCapabilities(byte[] historicalBytes) throws UsbTransportException { if ((historicalBytes == null) || (historicalBytes[0] != 0x00)) { throw new UsbTransportException("Invalid historical bytes category indicator byte"); } - - mCapabilityBytes = getCapabilitiesBytes(historicalBytes); + this.historicalBytes = historicalBytes; + capabilityBytes = getCapabilitiesBytes(historicalBytes); } public CardCapabilities() { - mCapabilityBytes = null; + capabilityBytes = null; } private static byte[] getCapabilitiesBytes(byte[] historicalBytes) { @@ -57,10 +65,34 @@ class CardCapabilities { } public boolean hasChaining() { - return mCapabilityBytes != null && (mCapabilityBytes[2] & MASK_CHAINING) != 0; + return capabilityBytes != null && (capabilityBytes[2] & MASK_CHAINING) != 0; } public boolean hasExtended() { - return mCapabilityBytes != null && (mCapabilityBytes[2] & MASK_EXTENDED) != 0; + return capabilityBytes != null && (capabilityBytes[2] & MASK_EXTENDED) != 0; + } + + public boolean hasResetSupport() throws UsbTransportException { + byte[] lastBytes = Arrays.copyOfRange(historicalBytes, historicalBytes.length - 2, historicalBytes.length); + boolean hasExpectedLastBytes = Arrays.equals(lastBytes, EXPECTED_PROCESSING_STATUS_BYTES); + + // Yk neo simply ends with 0x0000 + if (!hasExpectedLastBytes) { + return true; + } + + int statusIndicatorByte = historicalBytes[historicalBytes.length - 3]; + switch (statusIndicatorByte) { + case STATUS_INDICATOR_NO_INFORMATION: { + return false; + } + case STATUS_INDICATOR_INITIALISATION_STATE: + case STATUS_INDICATOR_OPERATIONAL_STATE: { + return true; + } + default: { + throw new UsbTransportException("Status indicator byte not specified in OpenPGP specification"); + } + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCommandApduFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCommandApduFactory.java index e5bc66611..719ab7e38 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCommandApduFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCommandApduFactory.java @@ -144,13 +144,13 @@ class OpenPgpCommandApduFactory { } @NonNull - CommandApdu createReactivate2Command() { - return CommandApdu.create(CLA, INS_ACTIVATE_FILE, P1_EMPTY, P2_EMPTY); + CommandApdu createReactivate1Command() { + return CommandApdu.create(CLA, INS_TERMINATE_DF, P1_EMPTY, P2_EMPTY); } @NonNull - CommandApdu createReactivate1Command() { - return CommandApdu.create(CLA, INS_TERMINATE_DF, P1_EMPTY, P2_EMPTY); + CommandApdu createReactivate2Command() { + return CommandApdu.create(CLA, INS_ACTIVATE_FILE, P1_EMPTY, P2_EMPTY); } @NonNull diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java index 826300fb5..2cff99b78 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenUtilsTest.java @@ -167,16 +167,31 @@ public class SecurityTokenUtilsTest extends Mockito { capabilities = new CardCapabilities(Hex.decode("007300008000000000000000000000")); Assert.assertEquals(capabilities.hasChaining(), true); Assert.assertEquals(capabilities.hasExtended(), false); + Assert.assertEquals(capabilities.hasResetSupport(), true); // Yk 4 capabilities = new CardCapabilities(Hex.decode("0073000080059000")); Assert.assertEquals(capabilities.hasChaining(), true); Assert.assertEquals(capabilities.hasExtended(), false); + Assert.assertEquals(capabilities.hasResetSupport(), true); // Nitrokey pro capabilities = new CardCapabilities(Hex.decode("0031c573c00140059000")); Assert.assertEquals(capabilities.hasChaining(), false); Assert.assertEquals(capabilities.hasExtended(), true); + Assert.assertEquals(capabilities.hasResetSupport(), true); + + // GNUK without Life Cycle Management + capabilities = new CardCapabilities(Hex.decode("00318473800180009000")); + Assert.assertEquals(capabilities.hasChaining(), true); + Assert.assertEquals(capabilities.hasExtended(), false); + Assert.assertEquals(capabilities.hasResetSupport(), false); + + // GNUK with Life Cycle Management: ./configure --enable-factory-reset + capabilities = new CardCapabilities(Hex.decode("00318473800180059000")); + Assert.assertEquals(capabilities.hasChaining(), true); + Assert.assertEquals(capabilities.hasExtended(), false); + Assert.assertEquals(capabilities.hasResetSupport(), true); } @Test