Split up ResetAndWipeTokenOp a bit, and add test
This commit is contained in:
@@ -102,6 +102,14 @@ public abstract class OpenPgpCapabilities {
|
|||||||
return getPwStatusBytes()[MAX_PW3_LENGTH_INDEX];
|
return getPwStatusBytes()[MAX_PW3_LENGTH_INDEX];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getPw1TriesLeft() {
|
||||||
|
return getPwStatusBytes()[4];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPw3TriesLeft() {
|
||||||
|
return getPwStatusBytes()[6];
|
||||||
|
}
|
||||||
|
|
||||||
@AutoValue.Builder
|
@AutoValue.Builder
|
||||||
@SuppressWarnings("UnusedReturnValue")
|
@SuppressWarnings("UnusedReturnValue")
|
||||||
abstract static class Builder {
|
abstract static class Builder {
|
||||||
|
|||||||
@@ -386,13 +386,14 @@ public class SecurityTokenConnection {
|
|||||||
byte[] aid = openPgpCapabilities.getAid();
|
byte[] aid = openPgpCapabilities.getAid();
|
||||||
String userId = parseHolderName(readUserId());
|
String userId = parseHolderName(readUserId());
|
||||||
String url = readUrl();
|
String url = readUrl();
|
||||||
byte[] pwInfo = openPgpCapabilities.getPwStatusBytes();
|
int pw1TriesLeft = openPgpCapabilities.getPw1TriesLeft();
|
||||||
|
int pw3TriesLeft = openPgpCapabilities.getPw3TriesLeft();
|
||||||
boolean hasLifeCycleManagement = cardCapabilities.hasLifeCycleManagement();
|
boolean hasLifeCycleManagement = cardCapabilities.hasLifeCycleManagement();
|
||||||
|
|
||||||
TransportType transportType = transport.getTransportType();
|
TransportType transportType = transport.getTransportType();
|
||||||
|
|
||||||
return SecurityTokenInfo.create(transportType, tokenType, fingerprints, aid, userId, url, pwInfo[4], pwInfo[6],
|
return SecurityTokenInfo.create(transportType, tokenType, fingerprints, aid, userId, url, pw1TriesLeft,
|
||||||
hasLifeCycleManagement);
|
pw3TriesLeft, hasLifeCycleManagement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection;
|
|||||||
|
|
||||||
|
|
||||||
public class ResetAndWipeTokenOp {
|
public class ResetAndWipeTokenOp {
|
||||||
|
private static final byte[] INVALID_PIN = "XXXXXXXXXXX".getBytes();
|
||||||
|
|
||||||
private final SecurityTokenConnection connection;
|
private final SecurityTokenConnection connection;
|
||||||
|
|
||||||
public static ResetAndWipeTokenOp create(SecurityTokenConnection connection) {
|
public static ResetAndWipeTokenOp create(SecurityTokenConnection connection) {
|
||||||
@@ -26,41 +28,16 @@ public class ResetAndWipeTokenOp {
|
|||||||
* Afterwards, the token is reactivated.
|
* Afterwards, the token is reactivated.
|
||||||
*/
|
*/
|
||||||
public void resetAndWipeToken() throws IOException {
|
public void resetAndWipeToken() throws IOException {
|
||||||
// try wrong PIN 4 times until counter goes to C0
|
exhausePw1Tries();
|
||||||
byte[] pin = "XXXXXX".getBytes();
|
exhaustPw3Tries();
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// secure messaging must be disabled before reactivation
|
// secure messaging must be disabled before reactivation
|
||||||
connection.clearSecureMessaging();
|
connection.clearSecureMessaging();
|
||||||
|
|
||||||
// reactivate token!
|
|
||||||
// NOTE: keep the order here! First execute _both_ reactivate commands. Before checking _both_ responses
|
// 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
|
// If a token is in a bad state and reactivate1 fails, it could still be reactivated with reactivate2
|
||||||
CommandApdu reactivate1 = connection.getCommandFactory().createReactivate1Command();
|
CommandApdu reactivate1 = connection.getCommandFactory().createReactivate1Command();
|
||||||
ResponseApdu response1 = connection.communicate(reactivate1);
|
connection.communicate(reactivate1);
|
||||||
if (!response1.isSuccess()) {
|
|
||||||
throw new CardException("Reactivating failed!", response1.getSw());
|
|
||||||
}
|
|
||||||
|
|
||||||
CommandApdu reactivate2 = connection.getCommandFactory().createReactivate2Command();
|
CommandApdu reactivate2 = connection.getCommandFactory().createReactivate2Command();
|
||||||
ResponseApdu response2 = connection.communicate(reactivate2);
|
ResponseApdu response2 = connection.communicate(reactivate2);
|
||||||
@@ -70,4 +47,29 @@ public class ResetAndWipeTokenOp {
|
|||||||
|
|
||||||
connection.refreshConnectionCapabilities();
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user