/****************************************************************************
* Copyright (C) 2013 ecsec GmbH.
* All rights reserved.
* Contact: ecsec GmbH (info@ecsec.de)
*
* This file is part of the Open eCard App.
*
* GNU General Public License Usage
* This file may be used under the terms of the GNU General Public
* License version 3.0 as published by the Free Software Foundation
* and appearing in the file LICENSE.GPL included in the packaging of
* this file. Please review the following information to ensure the
* GNU General Public License version 3.0 requirements will be met:
* http://www.gnu.org/copyleft/gpl.html.
*
* Other Usage
* Alternatively, this file may be used in accordance with the terms
* and conditions contained in a signed written agreement between
* you and ecsec GmbH.
*
***************************************************************************/
package org.openecard.crypto.common.sal;
import org.openecard.common.SecurityConditionUnsatisfiable;
import iso.std.iso_iec._24727.tech.schema.ACLList;
import iso.std.iso_iec._24727.tech.schema.ACLListResponse;
import iso.std.iso_iec._24727.tech.schema.AccessRuleType;
import iso.std.iso_iec._24727.tech.schema.ConnectionHandleType;
import iso.std.iso_iec._24727.tech.schema.CryptographicServiceActionName;
import iso.std.iso_iec._24727.tech.schema.NamedDataServiceActionName;
import iso.std.iso_iec._24727.tech.schema.DIDAuthenticationStateType;
import iso.std.iso_iec._24727.tech.schema.DIDGet;
import iso.std.iso_iec._24727.tech.schema.DIDGetResponse;
import iso.std.iso_iec._24727.tech.schema.DIDScopeType;
import iso.std.iso_iec._24727.tech.schema.DIDStructureType;
import iso.std.iso_iec._24727.tech.schema.SecurityConditionType;
import iso.std.iso_iec._24727.tech.schema.TargetNameType;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import org.openecard.common.WSHelper;
import org.openecard.common.WSHelper.WSException;
import org.openecard.common.interfaces.Dispatcher;
import org.openecard.common.interfaces.DispatcherException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Tobias Wich <tobias.wich@ecsec.de>
*/
public class ACLResolver {
private static final Logger logger = LoggerFactory.getLogger(ACLResolver.class);
private final Dispatcher dispatcher;
private final ConnectionHandleType handle;
public ACLResolver(Dispatcher dispatcher, ConnectionHandleType handle) {
this.dispatcher = dispatcher;
this.handle = WSHelper.copyHandle(handle);
}
public List<DIDStructureType> getUnsatisfiedDIDs(TargetNameType target) throws DispatcherException, WSException,
InvocationTargetException, SecurityConditionUnsatisfiable {
// get the ACL first
ACLList aclReq = new ACLList();
aclReq.setConnectionHandle(handle);
aclReq.setTargetName(target);
ACLListResponse aclRes = (ACLListResponse) dispatcher.deliver(aclReq);
WSHelper.checkResult(aclRes);
List<AccessRuleType> acls = aclRes.getTargetACL().getAccessRule();
List<DIDStructureType> dids = getMissingDids(acls, target);
return dids;
}
private List<DIDStructureType> getMissingDids(List<AccessRuleType> acls, TargetNameType target)
throws DispatcherException, InvocationTargetException, SecurityConditionUnsatisfiable, WSException {
// find the sign acl
ArrayList<AccessRuleType> tmpAcls = new ArrayList<AccessRuleType>();
for (AccessRuleType next : acls) {
if (target.getDIDName() != null) {
CryptographicServiceActionName action = next.getAction().getCryptographicServiceAction();
if (CryptographicServiceActionName.SIGN.equals(action)) {
tmpAcls.add(next);
break; // there can be only one
}
}
if (target.getDataSetName() != null) {
NamedDataServiceActionName action = next.getAction().getNamedDataServiceAction();
if (NamedDataServiceActionName.DATA_SET_SELECT.equals(action)) {
tmpAcls.add(next);
continue;
}
if (NamedDataServiceActionName.DSI_READ.equals(action)) {
tmpAcls.add(next);
continue;
}
}
}
ArrayList<DIDStructureType> result = new ArrayList<DIDStructureType>();
for (AccessRuleType acl : tmpAcls) {
// get the most suitable DID in the tree
SecurityConditionType cond = normalize(acl.getSecurityCondition());
cond = getBestSecurityCondition(cond);
// flatten condition to list of unsatisfied dids
List<DIDAuthenticationStateType> authStates = flattenCondition(cond);
List<DIDStructureType> missingDIDs = filterSatisfiedDIDs(authStates);
result.addAll(missingDIDs);
}
// remove duplicates
TreeSet<String> newDids = new TreeSet<String>();
Iterator<DIDStructureType> it = result.iterator();
while (it.hasNext()) {
// this code bluntly assumes, that did names are unique per cardinfo file
DIDStructureType next = it.next();
if (newDids.contains(next.getDIDName())) {
it.remove();
} else {
newDids.add(next.getDIDName());
}
}
return result;
}
private static SecurityConditionType normalize(SecurityConditionType cond) {
// in some cases the acl is super flat, make it disjunct
if (cond.getOr() == null) {
SecurityConditionType result = new SecurityConditionType();
SecurityConditionType.Or or = new SecurityConditionType.Or();
result.setOr(or);
or.getSecurityCondition().add(cond);
return result;
}
// TODO: implement correctly, for now we cross fingers and assume it is in disjunctive form
return cond;
}
private static SecurityConditionType getBestSecurityCondition(SecurityConditionType securityCondition) {
// TODO: do it right
// the condition is disjunctive, so we cross fingers and hope the first one is the best match
return securityCondition.getOr().getSecurityCondition().get(0);
}
private static List<DIDAuthenticationStateType> flattenCondition(SecurityConditionType conds)
throws SecurityConditionUnsatisfiable {
// the condition at this place must be in the form (A & B & C) or simply (A)
if (conds.getAnd() != null) {
ArrayList<DIDAuthenticationStateType> result = new ArrayList<DIDAuthenticationStateType>();
for (SecurityConditionType cond : conds.getAnd().getSecurityCondition()) {
// add all authentication states
DIDAuthenticationStateType state = cond.getDIDAuthentication();
if (state != null) {
result.add(cond.getDIDAuthentication());
}
}
return result;
} else if (conds.getDIDAuthentication() != null) {
ArrayList<DIDAuthenticationStateType> result = new ArrayList<DIDAuthenticationStateType>();
result.add(conds.getDIDAuthentication());
return result;
} else if (conds.isAlways() != null && conds.isAlways()) {
return Collections.emptyList();
} else if (conds.isNever() != null && conds.isNever()) {
String msg = "The ACL of the object states, that it is never satisfiable (never=true).";
throw new SecurityConditionUnsatisfiable(msg);
}
// TODO: add support for not cases
// nothing in the acl, we assum it is always=true
return Collections.emptyList();
}
private List<DIDStructureType> filterSatisfiedDIDs(List<DIDAuthenticationStateType> states)
throws DispatcherException, InvocationTargetException, WSException {
ArrayList<DIDStructureType> result = new ArrayList<DIDStructureType>(states.size());
for (DIDAuthenticationStateType state : states) {
if (state.isDIDState()) {
// perform DIDGet to see if the DID is authenticated
DIDGet req = new DIDGet();
req.setConnectionHandle(handle);
req.setDIDName(state.getDIDName());
req.setDIDScope(DIDScopeType.GLOBAL); // search everywhere
DIDGetResponse res = (DIDGetResponse) dispatcher.deliver(req);
WSHelper.checkResult(res);
// add it if not authenticated
if (! res.getDIDStructure().isAuthenticated()) {
result.add(res.getDIDStructure());
}
}
}
return result;
}
}