diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnection.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnection.java index 6b659c5f6..2d2a2a27e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnection.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnection.java @@ -23,6 +23,7 @@ package org.sufficientlysecure.keychain.securitytoken; import android.content.Context; import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1Integer; @@ -99,16 +100,18 @@ public class SecurityTokenConnection { public static SecurityTokenConnection getInstanceForTransport(Transport transport, Passphrase pin) { if (sCachedInstance == null || !sCachedInstance.isPersistentConnectionAllowed() || !sCachedInstance.isConnected() || !sCachedInstance.mTransport.equals(transport)) { - sCachedInstance = new SecurityTokenConnection(transport, pin); + sCachedInstance = new SecurityTokenConnection(transport, pin, new OpenPgpCommandApduFactory()); } return sCachedInstance; } - private SecurityTokenConnection(@NonNull Transport transport, @NonNull Passphrase pin) { + @VisibleForTesting + SecurityTokenConnection(@NonNull Transport transport, @NonNull Passphrase pin, + OpenPgpCommandApduFactory commandFactory) { this.mTransport = transport; this.mPin = pin; - commandFactory = new OpenPgpCommandApduFactory(); + this.commandFactory = commandFactory; } private String getHolderName(byte[] name) { @@ -172,7 +175,8 @@ public class SecurityTokenConnection { /** * Connect to device and select pgp applet */ - private void connectToDevice(Context context) throws IOException { + @VisibleForTesting + void connectToDevice(Context context) throws IOException { // Connect on transport layer mCardCapabilities = new CardCapabilities(); @@ -187,8 +191,8 @@ public class SecurityTokenConnection { throw new CardException("Initialization failed!", response.getSw()); } - mOpenPgpCapabilities = new OpenPgpCapabilities(getData(0x00, 0x6E)); - mCardCapabilities = new CardCapabilities(mOpenPgpCapabilities.getHistoricalBytes()); + OpenPgpCapabilities openPgpCapabilities = new OpenPgpCapabilities(getData(0x00, 0x6E)); + setConnectionCapabilities(openPgpCapabilities); mPw1ValidatedForSignature = false; mPw1ValidatedForDecrypt = false; @@ -202,7 +206,12 @@ public class SecurityTokenConnection { Log.e(Constants.TAG, "failed to establish secure messaging", e); } } + } + @VisibleForTesting + void setConnectionCapabilities(OpenPgpCapabilities openPgpCapabilities) throws IOException { + this.mOpenPgpCapabilities = openPgpCapabilities; + this.mCardCapabilities = new CardCapabilities(openPgpCapabilities.getHistoricalBytes()); } public void resetPin(byte[] newPin, Passphrase adminPin) throws IOException { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java index 26470a9f6..bfb17a3a3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java @@ -266,6 +266,8 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity return; } + Log.d(Constants.TAG, "security token exception", e); + // Otherwise, all status codes are fixed values. switch (status) { diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnectionTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnectionTest.java new file mode 100644 index 000000000..873536609 --- /dev/null +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnectionTest.java @@ -0,0 +1,114 @@ +package org.sufficientlysecure.keychain.securitytoken; + + +import java.io.IOException; + +import org.bouncycastle.util.encoders.Hex; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowLog; +import org.sufficientlysecure.keychain.KeychainTestRunner; +import org.sufficientlysecure.keychain.util.Passphrase; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + + +@RunWith(KeychainTestRunner.class) +public class SecurityTokenConnectionTest { + + @Before + public void setUp() throws Exception { + ShadowLog.stream = System.out; + } + + @Test + public void test_connectToDevice() throws Exception { + Transport transport = mock(Transport.class); + SecurityTokenConnection securityTokenConnection = + new SecurityTokenConnection(transport, new Passphrase("123456"), new OpenPgpCommandApduFactory()); + + String[] dialog = { "00a4040006d27600012401", "9000", + "00ca006e00", + "6e81de4f10d27600012401020000060364311500005f520f0073000080000000000000000000007381b7c00af" + + "00000ff04c000ff00ffc106010800001103c206010800001103c306010800001103c407007f7f7f03030" + + "3c53c4ec5fee25c4e89654d58cad8492510a89d3c3d8468da7b24e15bfc624c6a792794f15b7599915f7" + + "03aab55ed25424d60b17026b7b06c6ad4b9be30a3c63c000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000000000000000000000cd0" + + "c59cd0f2a59cd0af059cd0c959000" + }; + expect(transport, dialog); + + + securityTokenConnection.connectToDevice(RuntimeEnvironment.application); + + + verify(transport).connect(); + verifyDialog(transport, dialog); + } + + @Test + public void test_getTokenInfo() throws Exception { + Transport transport = mock(Transport.class); + SecurityTokenConnection securityTokenConnection = + new SecurityTokenConnection(transport, new Passphrase("123456"), new OpenPgpCommandApduFactory()); + OpenPgpCapabilities openPgpCapabilities = new OpenPgpCapabilities( + Hex.decode( + "6e81de4f10d27600012401020000060364311500005f520f0073000080000000000000000000007381b7c00af" + + "00000ff04c000ff00ffc106010800001103c206010800001103c306010800001103c407007f7f7f03" + + "0303c53c4ec5fee25c4e89654d58cad8492510a89d3c3d8468da7b24e15bfc624c6a792794f15b759" + + "9915f703aab55ed25424d60b17026b7b06c6ad4b9be30a3c63c000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000000000000000000000" + + "000000000cd0c59cd0f2a59cd0af059cd0c95" + )); + securityTokenConnection.setConnectionCapabilities(openPgpCapabilities); + + String[] dialog = { + "00ca006e00", + "6e81de4f10d27600012401020000060364311500005f520f0073000080000000000000000000007381b7c00af" + + "00000ff04c000ff00ffc106010800001103c206010800001103c306010800001103c407007f7f7f03030" + + "3c53c4ec5fee25c4e89654d58cad8492510a89d3c3d8468da7b24e15bfc624c6a792794f15b7599915f7" + + "03aab55ed25424d60b17026b7b06c6ad4b9be30a3c63c000000000000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000000000000000000000cd0" + + "c59cd0f2a59cd0af059cd0c959000", + "00ca004f00", + "d27600012401020000060364311500009000", + "00ca006500", + "65095b005f2d005f3501399000", + "00ca5f5000", + "9000", + "00ca00c400", + "007f7f7f0303039000" + }; + expect(transport, dialog); + + + securityTokenConnection.getTokenInfo(); + + + verifyDialog(transport, dialog); + } + + private void expect(Transport transport, String... dialog) throws IOException { + for (int i = 0; i < dialog.length; i += 2) { + CommandApdu command = CommandApdu.fromBytes(Hex.decode(dialog[i])); + ResponseApdu response = ResponseApdu.fromBytes(Hex.decode(dialog[i + 1])); + when(transport.transceive(eq(command))).thenReturn(response); + } + } + + private void verifyDialog(Transport transport, String... dialog) throws IOException { + InOrder inOrder = inOrder(transport); + for (int i = 0; i < dialog.length; i += 2) { + CommandApdu command = CommandApdu.fromBytes(Hex.decode(dialog[i])); + inOrder.verify(transport).transceive(eq(command)); + } + inOrder.verifyNoMoreInteractions(); + } +} \ No newline at end of file