package de.persosim.simulator.protocols.pin;
import java.util.ArrayList;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import de.persosim.simulator.apdu.CommandApduFactory;
import de.persosim.simulator.cardobjects.AuthObjectIdentifier;
import de.persosim.simulator.cardobjects.Iso7816LifeCycleState;
import de.persosim.simulator.cardobjects.MasterFile;
import de.persosim.simulator.cardobjects.PasswordAuthObject;
import de.persosim.simulator.cardobjects.PasswordAuthObjectWithRetryCounter;
import de.persosim.simulator.exception.AccessDeniedException;
import de.persosim.simulator.exception.LifeCycleChangeException;
import de.persosim.simulator.platform.CardStateAccessor;
import de.persosim.simulator.processing.ProcessingData;
import de.persosim.simulator.protocols.Oid;
import de.persosim.simulator.protocols.RoleOid;
import de.persosim.simulator.protocols.Tr03110;
import de.persosim.simulator.protocols.ta.AuthenticatedAuxiliaryData;
import de.persosim.simulator.protocols.ta.Authorization;
import de.persosim.simulator.protocols.ta.CertificateRole;
import de.persosim.simulator.protocols.ta.RelativeAuthorization;
import de.persosim.simulator.protocols.ta.TerminalAuthenticationMechanism;
import de.persosim.simulator.protocols.ta.TerminalType;
import de.persosim.simulator.seccondition.OrSecCondition;
import de.persosim.simulator.seccondition.PaceWithPasswordRunningSecurityCondition;
import de.persosim.simulator.seccondition.PaceWithPasswordSecurityCondition;
import de.persosim.simulator.seccondition.TaSecurityCondition;
import de.persosim.simulator.secstatus.AuthorizationStore;
import de.persosim.simulator.secstatus.EffectiveAuthorizationMechanism;
import de.persosim.simulator.secstatus.PaceMechanism;
import de.persosim.simulator.secstatus.SecStatus;
import de.persosim.simulator.secstatus.SecStatus.SecContext;
import de.persosim.simulator.secstatus.SecStatusMechanismUpdatePropagation;
import de.persosim.simulator.test.PersoSimTestCase;
import de.persosim.simulator.utils.BitField;
import de.persosim.simulator.utils.HexString;
import mockit.Deencapsulation;
import mockit.Expectations;
import mockit.Mocked;
public class PinProtocolTest extends PersoSimTestCase implements Tr03110 {
@Mocked CardStateAccessor mockedCardStateAccessor;
MasterFile mf;
SecStatus secStatus;
PasswordAuthObject authObject;
PasswordAuthObjectWithRetryCounter pinObject;
PinProtocol protocol;
TerminalAuthenticationMechanism taMechanismIs;
TerminalAuthenticationMechanism taMechanismAt;
EffectiveAuthorizationMechanism authMechanismAtPinMgmt;
@Before
public void setUp() throws Exception {
mf = new MasterFile();
secStatus= new SecStatus();
protocol = new PinProtocol();
protocol.setCardStateAccessor(mockedCardStateAccessor);
authObject = new PasswordAuthObject(new AuthObjectIdentifier(ID_PIN), "111111".getBytes(), "PIN");
TaSecurityCondition pinManagementCondition = new TaSecurityCondition(TerminalType.AT,
new RelativeAuthorization(CertificateRole.TERMINAL, new BitField(38).flipBit(5)));
pinObject = new PasswordAuthObjectWithRetryCounter(new AuthObjectIdentifier(ID_PIN), "111111".getBytes(), "PIN",
6, 6, 3, pinManagementCondition, new OrSecCondition(new PaceWithPasswordSecurityCondition("PIN"), new PaceWithPasswordSecurityCondition("PUK")),
new PaceWithPasswordSecurityCondition("PUK"),
new PaceWithPasswordRunningSecurityCondition("PIN"));
mf.addChild(pinObject);
pinObject.setSecStatus(secStatus);
pinObject.updateLifeCycleState(Iso7816LifeCycleState.OPERATIONAL_ACTIVATED);
taMechanismIs = new TerminalAuthenticationMechanism(new byte[]{1,2,3}, TerminalType.IS, new ArrayList<AuthenticatedAuxiliaryData>(), new byte[]{1,2,3}, new byte[]{1,2,3}, "test", null);
taMechanismAt = new TerminalAuthenticationMechanism(new byte[]{1,2,3}, TerminalType.AT, new ArrayList<AuthenticatedAuxiliaryData>(), new byte[]{1,2,3}, new byte[]{1,2,3}, "test", null);
HashMap<Oid, Authorization> authorizations = new HashMap<>();
authorizations.put(RoleOid.id_AT, new RelativeAuthorization(new BitField(40).flipBit(5)));
AuthorizationStore authStore = new AuthorizationStore(authorizations);
authMechanismAtPinMgmt = new EffectiveAuthorizationMechanism(authStore);
}
/**
* Positive test case. Send verify APDU to the simulator
* and receives a 63Cx where x stands for the number of left retries.
*/
@Test
public void testProcessCommandVerifyPassword() throws Exception {
// prepare the mock
new Expectations() {
{
mockedCardStateAccessor.getMasterFile();
result = mf;
}
};
// select Apdu
ProcessingData processingData = new ProcessingData();
byte[] apduBytes = HexString.toByteArray("00200003");
processingData.updateCommandApdu(this, "select file APDU",
CommandApduFactory.createCommandApdu(apduBytes));
// call mut
protocol.process(processingData);
// check results
assertEquals("Statusword", SW_63C3_COUNTER_IS_3, processingData
.getResponseApdu().getStatusWord());
assertEquals("RetryCounterValue", 3, pinObject.getRetryCounterCurrentValue());
}
/**
* Negative test case. Send verify apdu to the simulator but the PinObject
* is null
*/
@Test
public void testProcessCommandVerifyPassword_PasswordObjectIsNull() {
// prepare the mock
mf = new MasterFile(); //use empty MF
new Expectations() {
{
mockedCardStateAccessor.getMasterFile();
result = mf;
}
};
// select Apdu
ProcessingData processingData = new ProcessingData();
byte[] apduBytes = HexString.toByteArray("00200003");
processingData.updateCommandApdu(this, "select file APDU",
CommandApduFactory.createCommandApdu(apduBytes));
// call mut
protocol.process(processingData);
// check results
assertEquals("Statusword", SW_6984_REFERENCE_DATA_NOT_USABLE,
processingData.getResponseApdu().getStatusWord());
}
/**
* Positive test case. Send changePin APDU to the simulator and receives a
* 9000 if the PIN was successfully changed.
*/
@Test
public void testProcessCommandChangePassword() throws Exception {
// prepare the mock
secStatus.updateMechanisms(new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, new PaceMechanism(pinObject, null, null)));
new Expectations() {
{
mockedCardStateAccessor.getMasterFile();
result = mf;
}
};
// select Apdu
ProcessingData processingData = new ProcessingData();
byte[] apduBytes = HexString.toByteArray("002C020306323232323232");
processingData.updateCommandApdu(this, "select file APDU",
CommandApduFactory.createCommandApdu(apduBytes));
// call mut
protocol.process(processingData);
// check results
assertEquals("Statusword", SW_9000_NO_ERROR, processingData
.getResponseApdu().getStatusWord());
assertArrayEquals("Password", "222222".getBytes(), pinObject.getPassword());
}
/**
* Negative test case. Send changePin APDU to the simulator
* and receives a 6984 because the object has no retry counter.
*/
@Test
public void testProcessCommandChangePassword_NoRertyCnt() throws Exception {
// prepare the mock
new Expectations() {
{
mockedCardStateAccessor.getMasterFile();
result = mf;
}
};
// select Apdu
ProcessingData processingData = new ProcessingData();
byte[] apduBytes = HexString.toByteArray("002C020306313432353336");
processingData.updateCommandApdu(this, "select file APDU",
CommandApduFactory.createCommandApdu(apduBytes));
// call mut
protocol.process(processingData);
// check results
assertEquals("Statusword", SW_6982_SECURITY_STATUS_NOT_SATISFIED, processingData
.getResponseApdu().getStatusWord());
assertArrayEquals("Password", "111111".getBytes(), authObject.getPassword());
}
/**
* Negative test case. Send changePin APDU with no pin
* to the simulator (tlvData is empty) and receives 6A80.
*/
@Test
public void testProcessCommandChangePassword_EmptyPassword() throws Exception {
// prepare the mock
new Expectations() {
{
mockedCardStateAccessor.getMasterFile();
result = mf;
}
};
// select Apdu
ProcessingData processingData = new ProcessingData();
byte[] apduBytes = HexString.toByteArray("002C0203");
processingData.updateCommandApdu(this, "select file APDU",
CommandApduFactory.createCommandApdu(apduBytes));
// call mut
protocol.process(processingData);
// check results
assertEquals("Statusword", SW_6A80_WRONG_DATA, processingData
.getResponseApdu().getStatusWord());
assertArrayEquals("Password", "111111".getBytes(), pinObject.getPassword());
}
/**
* Positive test case. Send apdu to unblock PIN and receives 9000
*/
@Test
public void testProcessCommandUnblockPassword_PasswordBlocked() throws Exception {
// prepare the mock
Deencapsulation.setField(pinObject, "retryCounterCurrentValue", 0);
secStatus.updateMechanisms(new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, taMechanismAt));
secStatus.updateMechanisms(new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, authMechanismAtPinMgmt));
new Expectations() {
{
mockedCardStateAccessor.getMasterFile();
result = mf;
}
};
// select Apdu
ProcessingData processingData = new ProcessingData();
byte[] apduBytes = HexString.toByteArray("002C0303");
processingData.updateCommandApdu(this, "select file APDU",
CommandApduFactory.createCommandApdu(apduBytes));
// call mut
protocol.process(processingData);
// check results
assertEquals("Statusword is 9000", SW_9000_NO_ERROR, processingData
.getResponseApdu().getStatusWord());
assertEquals("RetryCounterValue", 3, pinObject.getRetryCounterCurrentValue());
}
/**
* Negative test case. Send apdu to unblock PIN
* but the PIN is already unblocked (retry counter is 3).
*/
@Test
public void testProcessCommandUnblockPassword_PasswordUnblocked() throws Exception {
// prepare the mock
secStatus.updateMechanisms(new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, taMechanismAt));
secStatus.updateMechanisms(new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, authMechanismAtPinMgmt));
new Expectations() {
{
mockedCardStateAccessor.getMasterFile();
result = mf;
}
};
// select Apdu
ProcessingData processingData = new ProcessingData();
byte[] apduBytes = HexString.toByteArray("002C0303");
processingData.updateCommandApdu(this, "select file APDU",
CommandApduFactory.createCommandApdu(apduBytes));
// call mut
protocol.process(processingData);
// check results
assertEquals("Statusword", SW_9000_NO_ERROR, processingData
.getResponseApdu().getStatusWord());
assertEquals("RetryCounterValue", 3, pinObject.getRetryCounterCurrentValue());
}
/**
* Positive test case. Send apdu to activate the PIN and receives
* a 9000.
* @throws LifeCycleChangeException
*/
@Test
public void testProcessCommandActivatePassword() throws Exception {
// prepare the mock
Deencapsulation.setField(pinObject, "lifeCycleState", Iso7816LifeCycleState.OPERATIONAL_DEACTIVATED);
secStatus.updateMechanisms(new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, taMechanismAt));
secStatus.updateMechanisms(new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, authMechanismAtPinMgmt));
new Expectations() {
{
mockedCardStateAccessor.getMasterFile();
result = mf;
}
};
// select Apdu
ProcessingData processingData = new ProcessingData();
byte[] apduBytes = HexString.toByteArray("00441003");
processingData.updateCommandApdu(this, "select file APDU",
CommandApduFactory.createCommandApdu(apduBytes));
// call mut
protocol.process(processingData);
// check results
assertEquals("Statusword", SW_9000_NO_ERROR, processingData
.getResponseApdu().getStatusWord());
assertEquals("Lifecycle", Iso7816LifeCycleState.OPERATIONAL_ACTIVATED, pinObject.getLifeCycleState());
}
/**
* Positive test case. Send apdu to deactivate the PIN an receives a 9000.
* @throws AccessDeniedException
*/
@Test
public void testProcessCommandDeactivatePassword() throws Exception {
// prepare the mock
secStatus.updateMechanisms(new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, taMechanismAt));
secStatus.updateMechanisms(new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, authMechanismAtPinMgmt));
new Expectations() {
{
mockedCardStateAccessor.getMasterFile();
result = mf;
}
};
// select Apdu
ProcessingData processingData = new ProcessingData();
byte[] apduBytes = HexString.toByteArray("00041003");
processingData.updateCommandApdu(this, "select file APDU",
CommandApduFactory.createCommandApdu(apduBytes));
// call mut
protocol.process(processingData);
// check results
assertEquals("Statusword", SW_9000_NO_ERROR, processingData
.getResponseApdu().getStatusWord());
assertEquals("Lifecycle", Iso7816LifeCycleState.OPERATIONAL_DEACTIVATED, pinObject.getLifeCycleState());
}
/**
* Negative test case. Send apdu to deactivate the Pin but Pin
* management rights from TA are required to perform the deactivate.
*/
@Test
public void testProcessCommandDeactivatePassword_SecStatusNotSatisfied() throws Exception {
// prepare the mock
new Expectations() {
{
mockedCardStateAccessor.getMasterFile();
result = mf;
}
};
// select Apdu
ProcessingData processingData = new ProcessingData();
byte[] apduBytes = HexString.toByteArray("00041003");
processingData.updateCommandApdu(this, "select file APDU",
CommandApduFactory.createCommandApdu(apduBytes));
// call mut
protocol.process(processingData);
// check results
assertEquals("Statusword",
SW_6982_SECURITY_STATUS_NOT_SATISFIED, processingData
.getResponseApdu().getStatusWord());
}
}