package de.persosim.simulator.secstatus;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import org.globaltester.logging.InfoSource;
import de.persosim.simulator.apdu.ResponseApdu;
import de.persosim.simulator.cardobjects.CardObject;
import de.persosim.simulator.cardobjects.Iso7816LifeCycleState;
import de.persosim.simulator.platform.CommandProcessor;
import de.persosim.simulator.platform.Iso7816;
import de.persosim.simulator.processing.ProcessingData;
import de.persosim.simulator.processing.UpdatePropagation;
import de.persosim.simulator.seccondition.SecCondition;
import de.persosim.simulator.securemessaging.SmDataProviderGenerator;
/**
* Representation of the current security status of the card.
*
* The SecStatus is owned and managed by the {@link CommandProcessor}. The
* active protocols can query the SecStatus through a facade provided during
* protocol initialization and modify by adding UpdatePropagations to the
* ProcessingData.
*
* @author amay
*
*/
public class SecStatus implements InfoSource{
public enum SecContext {
GLOBAL, APPLICATION, FILE, COMMAND, PERSISTANT
}
EnumMap<SecContext, HashMap<Class<? extends SecMechanism>, SecMechanism>> contexts = new EnumMap<>(
SecContext.class);
HashMap<Integer, EnumMap<SecContext, HashMap<Class<? extends SecMechanism>, SecMechanism>>> storedSecStatusContents = new HashMap<>();
public SecStatus() {
reset();
}
/**
* Resets the complete contents of this security status.
*/
public void reset() {
reset(true);
}
/**
* Resets the security status. The parameter decides if a complete reset of
* all inner state is performed. In case of a partial reset the
* {@link SecContext#PERSISTANT} is not cleared.
*
* @param completeReset
*/
private void reset(boolean completeReset) {
// initialize the contexts
for (SecContext curSecContext : SecContext.values()) {
if (!completeReset && curSecContext == SecContext.PERSISTANT && contexts.containsKey(SecContext.PERSISTANT)){
continue;
}
contexts.put(curSecContext, new HashMap<Class<? extends SecMechanism>, SecMechanism>());
}
}
/**
* This method finds all currently active mechanisms (instances) as defined
* by wantedMechanisms.
*
* @param context
* to be searched for mechanisms
* @param wantedMechanisms
* as classes to be matched on
* @return all wanted SecMechanism instances in the given context
*/
public Collection<SecMechanism> getCurrentMechanisms(SecContext context,
Collection<Class<? extends SecMechanism>> wantedMechanisms) {
HashSet<SecMechanism> result = new HashSet<>();
for (Class<? extends SecMechanism> clazz : wantedMechanisms) {
HashMap<Class<? extends SecMechanism>, SecMechanism> securityContext = contexts.get(context);
if (securityContext.containsKey(clazz)) {
result.add(contexts.get(context).get(clazz));
}
}
return result;
}
/**
* This method updates internal state of the SecStatus according to the
* UpdatePropagation.
*
* @param updatePropagation
*/
public void updateMechanisms(SecStatusMechanismUpdatePropagation... updatePropagation) {
for (SecStatusMechanismUpdatePropagation curUpdate : updatePropagation) {
SecStatusMechanismUpdatePropagation mechanismPropagation = (SecStatusMechanismUpdatePropagation) curUpdate;
updateContext(mechanismPropagation.getContext(), mechanismPropagation.getMechanism());
}
}
private void updateContext(SecContext context, SecMechanism mechanism) {
contexts.get(context).put(mechanism.getKey(), mechanism);
}
/**
* This method updates internal state of the SecStatus according to the
* UpdatePropagation.
*
* @param updatePropagation
*/
private void updateEvents(SecStatusEventUpdatePropagation... updatePropagation) {
for (SecStatusEventUpdatePropagation curUpdate : updatePropagation) {
SecStatusEventUpdatePropagation eventPropagation = (SecStatusEventUpdatePropagation) curUpdate;
for (SecContext context : contexts.keySet()) {
Collection<Class<? extends SecMechanism>> toBeDeleted = new HashSet<Class<? extends SecMechanism>>();
for (Class<? extends SecMechanism> clazz : contexts.get(context).keySet()) {
if (contexts.get(context).get(clazz).needsDeletionInCaseOf(eventPropagation.getEvent())) {
toBeDeleted.add(clazz);
}
}
for (Class<? extends SecMechanism> clazz : toBeDeleted) {
contexts.get(context).remove(clazz);
}
}
}
}
/**
* This method updates internal state of the SecStatus according to the
* UpdatePropagations contained in the processing data.
*
* Called by the {@link CommandProcessor} during processing of each APDU.
*
* @param processingData
*/
public void updateSecStatus(ProcessingData processingData) {
for (UpdatePropagation update : processingData.getUpdatePropagations(SecStatusStoreUpdatePropagation.class)) {
storeRestoreSession(processingData,(SecStatusStoreUpdatePropagation) update);
}
for (UpdatePropagation update : processingData.getUpdatePropagations(SecStatusEventUpdatePropagation.class)) {
updateEvents((SecStatusEventUpdatePropagation) update);
}
for (UpdatePropagation update : processingData
.getUpdatePropagations(SecStatusMechanismUpdatePropagation.class)) {
updateMechanisms((SecStatusMechanismUpdatePropagation) update);
}
}
/**
* This stores all {@link SecMechanism}s currently existing in the
* {@link SecStatus}. Already existing stored contents are replaced when the
* same id is reused.
*
* @param id
* the integer id to reference the stored contents
*/
public void storeSecStatus(int id) {
storedSecStatusContents.put(id, createCopyForStoring(contexts));
}
/**
* This calls {@link #storeSecStatus(int)} using a unused id and returns it
* for further use.
*
* @see #storeSecStatus(int)
* @return the id used for storing the {@link SecStatus} contents
*/
public int storeSecStatus() {
int freeId = 0;
while (storedSecStatusContents.containsKey(freeId)) {
freeId++;
}
storeSecStatus(freeId);
return freeId;
}
/**
* Restores the previously stored contents to be the current content of the
* {@link SecStatus}. This implicitly removes all {@link SecMechanism}s that
* were added or differ from the ones in the restored state.
*
* @param id
* the id of the stored contents to be restored
* @throws IllegalArgumentException
* if the no contents were stored using the given id
*/
public void restoreSecStatus(int id) {
EnumMap<SecContext, HashMap<Class<? extends SecMechanism>, SecMechanism>> toRestore = storedSecStatusContents
.get(id);
if (toRestore == null) {
throw new IllegalArgumentException("The given id does not exist in the stored contents.");
}
reset(false);
for (SecContext context : toRestore.keySet()) {
for (SecMechanism mechanism : toRestore.get(context).values()) {
updateContext(context, mechanism);
}
}
}
// TODO move this suppression into the cast as soon as compliance level 1.8
// is used for this code
/**
* Creates a copy of the data structure storing the {@link SecMechanism}s.
* The returned object is a duplicate using the same references to
* {@link SecMechanism} objects. This relies on the immutability of the
* {@link SecMechanism}s.
*
* @param source
* the object to copy
* @return the copied object
*/
@SuppressWarnings("unchecked") // This suppression is needed because
// Object.clone() does not support generics
private EnumMap<SecContext, HashMap<Class<? extends SecMechanism>, SecMechanism>> createCopyForStoring(
EnumMap<SecContext, HashMap<Class<? extends SecMechanism>, SecMechanism>> source) {
EnumMap<SecContext, HashMap<Class<? extends SecMechanism>, SecMechanism>> copy = new EnumMap<>(
SecContext.class);
for (SecContext context : source.keySet()) {
if (context == SecContext.PERSISTANT){
continue;
}
copy.put(context, (HashMap<Class<? extends SecMechanism>, SecMechanism>) source.get(context).clone());
}
return copy;
}
/**
* Processes all restore session context event update propagations. Calls for every event the appropriate function either
* storeSecStatus or restoreSecStatus. If the SecStatus has to be restored this function also set the {@link SmDataProviderGenerator}
* to restore all needed keys for the securemessaging.
*
* @param processingData the processind data
* @param update the SecStatusStoreUpdatePropagation
*/
private void storeRestoreSession(ProcessingData processingData, SecStatusStoreUpdatePropagation ... update) {
try {
for (SecStatusStoreUpdatePropagation curUpdate : update) {
SecStatusStoreUpdatePropagation eventPropagation = (SecStatusStoreUpdatePropagation) curUpdate;
if (eventPropagation.getEvent().equals(SecurityEvent.RESTORE_SESSION_CONTEXT)){
restoreSecStatus(eventPropagation.getSessionContextIdentifier());
HashSet<Class<? extends SecMechanism>> set = new HashSet<>();
set.add(SmDataProviderGenerator.class);
Collection<SecMechanism> generators = getCurrentMechanisms(SecContext.APPLICATION, set);
if (generators.size() > 1){
processingData.updateResponseAPDU(this, "More than one secure messaging context found", new ResponseApdu(Iso7816.SW_6400_EXECUTION_ERROR));
}
if (generators.size() == 1){
processingData.addUpdatePropagation(this, "restore Secure Messaging", ((SmDataProviderGenerator)generators.iterator().next()).generateSmDataProvider());
}
}
if (eventPropagation.getEvent().equals(SecurityEvent.STORE_SESSION_CONTEXT)){
storeSecStatus(eventPropagation.getSessionContextIdentifier());
}
}
} catch(IllegalArgumentException e) {
processingData.updateResponseAPDU(this, e.getMessage(), new ResponseApdu(Iso7816.SW_6A88_REFERENCE_DATA_NOT_FOUND));
}
}
/**
* This method can be used to check whether necessary access conditions are
* fulfilled. It uses the application security context.
*
* @param state
* the lifecycle state of the {@link CardObject}
* @param secCondition
* the {@link SecCondition} to verify
* @return true, if at least one security condition is fulfilled or the
* {@link Iso7816LifeCycleState} grants access
*/
public boolean checkAccessConditions(Iso7816LifeCycleState state, SecCondition secCondition){
return checkAccessConditions(state, secCondition, SecContext.APPLICATION);
}
/**
* This method can be used to check whether necessary access conditions are
* fulfilled.
*
* @param state
* the lifecycle state of the {@link CardObject}
* @param secCondition
* the {@link SecCondition} to verify. Must not be null.
* @param context
* {@link SecContext} the context to check the conditions for
* @return true, if at least one security condition is fulfilled or the
* {@link Iso7816LifeCycleState} grants access
*/
public boolean checkAccessConditions(Iso7816LifeCycleState state, SecCondition secCondition, SecContext context){
if (checkAccessConditions(state)){
return true;
} else if (secCondition.check(this.getCurrentMechanisms(context, secCondition.getNeededMechanisms()))){
return true;
} else {
return false;
}
}
/**
* This only checks the lifecycle state for necessary access conditions.
*
* @param state
* the lifecycle state of the {@link CardObject}
* @return true, if the {@link Iso7816LifeCycleState} grants access
*/
public static boolean checkAccessConditions(Iso7816LifeCycleState state){
if (state.equals(Iso7816LifeCycleState.CREATION)){
return true;
}
return false;
// IMPL the implementation of checks regarding the initialization state
// is missing, as it is not yet needed since personalization happens before starting the simulator
}
@Override
public String getIDString() {
return "SecStatus";
}
}