From 442845f1fe4ba3218ef99945d45e76fbd5843f77 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 13 Jan 2018 16:28:26 +0100 Subject: [PATCH] Split up ResetAndWipeTokenOp a bit, and add test --- .../securitytoken/OpenPgpCapabilities.java | 8 ++ .../SecurityTokenConnection.java | 7 +- .../operations/ResetAndWipeTokenOp.java | 58 +++++++------- .../operations/ResetAndWipeTokenOpTest.java | 77 +++++++++++++++++++ 4 files changed, 119 insertions(+), 31 deletions(-) create mode 100644 OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/operations/ResetAndWipeTokenOpTest.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java index 064091b62..362e8bc55 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/OpenPgpCapabilities.java @@ -102,6 +102,14 @@ public abstract class OpenPgpCapabilities { return getPwStatusBytes()[MAX_PW3_LENGTH_INDEX]; } + public int getPw1TriesLeft() { + return getPwStatusBytes()[4]; + } + + public int getPw3TriesLeft() { + return getPwStatusBytes()[6]; + } + @AutoValue.Builder @SuppressWarnings("UnusedReturnValue") abstract static class Builder { 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 4471188df..dd4277a31 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnection.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnection.java @@ -386,13 +386,14 @@ public class SecurityTokenConnection { byte[] aid = openPgpCapabilities.getAid(); String userId = parseHolderName(readUserId()); String url = readUrl(); - byte[] pwInfo = openPgpCapabilities.getPwStatusBytes(); + int pw1TriesLeft = openPgpCapabilities.getPw1TriesLeft(); + int pw3TriesLeft = openPgpCapabilities.getPw3TriesLeft(); boolean hasLifeCycleManagement = cardCapabilities.hasLifeCycleManagement(); TransportType transportType = transport.getTransportType(); - return SecurityTokenInfo.create(transportType, tokenType, fingerprints, aid, userId, url, pwInfo[4], pwInfo[6], - hasLifeCycleManagement); + return SecurityTokenInfo.create(transportType, tokenType, fingerprints, aid, userId, url, pw1TriesLeft, + pw3TriesLeft, hasLifeCycleManagement); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/ResetAndWipeTokenOp.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/ResetAndWipeTokenOp.java index 2befafe0d..c4347cb41 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/ResetAndWipeTokenOp.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/operations/ResetAndWipeTokenOp.java @@ -10,6 +10,8 @@ import org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection; public class ResetAndWipeTokenOp { + private static final byte[] INVALID_PIN = "XXXXXXXXXXX".getBytes(); + private final SecurityTokenConnection connection; public static ResetAndWipeTokenOp create(SecurityTokenConnection connection) { @@ -26,41 +28,16 @@ public class ResetAndWipeTokenOp { * Afterwards, the token is reactivated. */ public void resetAndWipeToken() throws IOException { - // try wrong PIN 4 times until counter goes to C0 - byte[] pin = "XXXXXX".getBytes(); - CommandApdu verifyPw1ForSignatureCommand = - connection.getCommandFactory().createVerifyPw1ForSignatureCommand(pin); - for (int i = 0; i <= 4; i++) { - // Command APDU for VERIFY command (page 32) - ResponseApdu response = connection.communicate(verifyPw1ForSignatureCommand); - if (response.isSuccess()) { - throw new CardException("Should never happen, XXXXXX has been accepted!", response.getSw()); - } - } - - // try wrong Admin PIN 4 times until counter goes to C0 - byte[] adminPin = "XXXXXXXX".getBytes(); - CommandApdu verifyPw3Command = connection.getCommandFactory().createVerifyPw3Command(adminPin); - for (int i = 0; i <= 4; i++) { - // Command APDU for VERIFY command (page 32) - ResponseApdu response = connection.communicate( - verifyPw3Command); - if (response.isSuccess()) { // Should NOT accept! - throw new CardException("Should never happen, XXXXXXXX has been accepted", response.getSw()); - } - } + exhausePw1Tries(); + exhaustPw3Tries(); // secure messaging must be disabled before reactivation connection.clearSecureMessaging(); - // reactivate token! // NOTE: keep the order here! First execute _both_ reactivate commands. Before checking _both_ responses // If a token is in a bad state and reactivate1 fails, it could still be reactivated with reactivate2 CommandApdu reactivate1 = connection.getCommandFactory().createReactivate1Command(); - ResponseApdu response1 = connection.communicate(reactivate1); - if (!response1.isSuccess()) { - throw new CardException("Reactivating failed!", response1.getSw()); - } + connection.communicate(reactivate1); CommandApdu reactivate2 = connection.getCommandFactory().createReactivate2Command(); ResponseApdu response2 = connection.communicate(reactivate2); @@ -70,4 +47,29 @@ public class ResetAndWipeTokenOp { connection.refreshConnectionCapabilities(); } + + private void exhausePw1Tries() throws IOException { + CommandApdu verifyPw1ForSignatureCommand = + connection.getCommandFactory().createVerifyPw1ForSignatureCommand(INVALID_PIN); + + int pw1TriesLeft = Math.max(3, connection.getOpenPgpCapabilities().getPw1TriesLeft()); + for (int i = 0; i < pw1TriesLeft; i++) { + ResponseApdu response = connection.communicate(verifyPw1ForSignatureCommand); + if (response.isSuccess()) { + throw new CardException("Should never happen, PIN XXXXXXXX has been accepted!", response.getSw()); + } + } + } + + private void exhaustPw3Tries() throws IOException { + CommandApdu verifyPw3Command = connection.getCommandFactory().createVerifyPw3Command(INVALID_PIN); + + int pw3TriesLeft = Math.max(3, connection.getOpenPgpCapabilities().getPw3TriesLeft()); + for (int i = 0; i < pw3TriesLeft; i++) { + ResponseApdu response = connection.communicate(verifyPw3Command); + if (response.isSuccess()) { // Should NOT accept! + throw new CardException("Should never happen, PIN XXXXXXXX has been accepted!", response.getSw()); + } + } + } } diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/operations/ResetAndWipeTokenOpTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/operations/ResetAndWipeTokenOpTest.java new file mode 100644 index 000000000..c808a0c8f --- /dev/null +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/securitytoken/operations/ResetAndWipeTokenOpTest.java @@ -0,0 +1,77 @@ +package org.sufficientlysecure.keychain.securitytoken.operations; + + +import org.bouncycastle.util.encoders.Hex; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.sufficientlysecure.keychain.KeychainTestRunner; +import org.sufficientlysecure.keychain.securitytoken.CommandApdu; +import org.sufficientlysecure.keychain.securitytoken.OpenPgpCapabilities; +import org.sufficientlysecure.keychain.securitytoken.OpenPgpCommandApduFactory; +import org.sufficientlysecure.keychain.securitytoken.ResponseApdu; +import org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + + +@SuppressWarnings("WeakerAccess") +@RunWith(KeychainTestRunner.class) +public class ResetAndWipeTokenOpTest { + static final ResponseApdu RESPONSE_APDU_SUCCESS = ResponseApdu.fromBytes(Hex.decode("9000")); + static final ResponseApdu RESPONSE_APDU_BAD_PW = ResponseApdu.fromBytes(Hex.decode("63C0")); + + SecurityTokenConnection securityTokenConnection; + OpenPgpCommandApduFactory commandFactory; + ResetAndWipeTokenOp useCase; + + @Before + public void setUp() throws Exception { + securityTokenConnection = mock(SecurityTokenConnection.class); + + commandFactory = mock(OpenPgpCommandApduFactory.class); + when(securityTokenConnection.getCommandFactory()).thenReturn(commandFactory); + + useCase = ResetAndWipeTokenOp.create(securityTokenConnection); + } + + @Test + public void resetAndWipeToken() throws Exception { + OpenPgpCapabilities openPgpCapabilities = OpenPgpCapabilities.fromBytes( + Hex.decode("6e81de4f10d27600012401020000060364311500005f520f0073000080000000000000000000007381b7c00af" + + "00000ff04c000ff00ffc106010800001103c206010800001103c306010800001103c407007f7f7f03" + + "0303c53c4ec5fee25c4e89654d58cad8492510a89d3c3d8468da7b24e15bfc624c6a792794f15b759" + + "9915f703aab55ed25424d60b17026b7b06c6ad4b9be30a3c63c000000000000000000000000000000" + + "000000000000000000000000000000000000000000000000000000000000000000000000000000000" + + "000000000cd0c59cd0f2a59cd0af059cd0c95" + )); + when(securityTokenConnection.getOpenPgpCapabilities()).thenReturn(openPgpCapabilities); + + CommandApdu verifyPw1Apdu = mock(CommandApdu.class); + CommandApdu verifyPw3Apdu = mock(CommandApdu.class); + when(commandFactory.createVerifyPw1ForSignatureCommand(any(byte[].class))).thenReturn(verifyPw1Apdu); + when(commandFactory.createVerifyPw3Command(any(byte[].class))).thenReturn(verifyPw3Apdu); + when(securityTokenConnection.communicate(verifyPw1Apdu)).thenReturn(RESPONSE_APDU_BAD_PW); + when(securityTokenConnection.communicate(verifyPw3Apdu)).thenReturn(RESPONSE_APDU_BAD_PW); + + CommandApdu reactivate1Apdu = mock(CommandApdu.class); + CommandApdu reactivate2Apdu = mock(CommandApdu.class); + when(commandFactory.createReactivate1Command()).thenReturn(reactivate1Apdu); + when(commandFactory.createReactivate2Command()).thenReturn(reactivate2Apdu); + when(securityTokenConnection.communicate(reactivate1Apdu)).thenReturn(RESPONSE_APDU_SUCCESS); + when(securityTokenConnection.communicate(reactivate2Apdu)).thenReturn(RESPONSE_APDU_SUCCESS); + + + useCase.resetAndWipeToken(); + + + verify(securityTokenConnection).communicate(reactivate1Apdu); + verify(securityTokenConnection).communicate(reactivate2Apdu); + verify(securityTokenConnection).refreshConnectionCapabilities(); + } + +} \ No newline at end of file