From bee5dd259a4438d45ecd1bcc26dfba12875236d6 Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Tue, 26 Jun 2018 11:03:48 +0200 Subject: initial commit --- .../idp/auth/AbstractAuthenticationManager.java | 341 +++++++++++++++ .../eaaf/core/impl/idp/auth/RequestStorage.java | 119 ++++++ .../builder/AbstractAuthenticationDataBuilder.java | 467 +++++++++++++++++++++ .../core/impl/idp/auth/builder/BPKBuilder.java | 302 +++++++++++++ .../impl/idp/auth/data/AuthProcessDataWrapper.java | 219 ++++++++++ .../eaaf/core/impl/idp/auth/data/IdentityLink.java | 312 ++++++++++++++ .../data/SimpleIdentityLinkAssertionParser.java | 326 ++++++++++++++ .../idp/auth/modules/AbstractAuthServletTask.java | 220 ++++++++++ .../impl/idp/auth/modules/ModuleRegistration.java | 151 +++++++ 9 files changed, 2457 insertions(+) create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/AbstractAuthenticationManager.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/RequestStorage.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/AbstractAuthenticationDataBuilder.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/BPKBuilder.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/AuthProcessDataWrapper.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/IdentityLink.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/SimpleIdentityLinkAssertionParser.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/AbstractAuthServletTask.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/ModuleRegistration.java (limited to 'eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth') diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/AbstractAuthenticationManager.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/AbstractAuthenticationManager.java new file mode 100644 index 00000000..e52a7884 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/AbstractAuthenticationManager.java @@ -0,0 +1,341 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.core.impl.idp.auth; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.IRequestStorage; +import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; +import at.gv.egiz.eaaf.core.api.idp.auth.IAuthenticationManager; +import at.gv.egiz.eaaf.core.api.idp.auth.ISSOManager; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.api.idp.process.ProcessEngine; +import at.gv.egiz.eaaf.core.api.logging.IRevisionLogger; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.exceptions.EAAFSSOException; +import at.gv.egiz.eaaf.core.exceptions.NoPassivAuthenticationException; +import at.gv.egiz.eaaf.core.exceptions.ProcessExecutionException; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.ModuleRegistration; +import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl; +import at.gv.egiz.eaaf.core.impl.idp.process.ExecutionContextImpl; +import at.gv.egiz.eaaf.core.impl.utils.TransactionIDUtils; + +public abstract class AbstractAuthenticationManager implements IAuthenticationManager { + private static final Logger log = LoggerFactory.getLogger(AbstractAuthenticationManager.class); + + private static List reqParameterWhiteListeForModules = new ArrayList(); + private static List reqHeaderWhiteListeForModules = new ArrayList(); + + public static final String MOA_SESSION = "MoaAuthenticationSession"; + public static final String MOA_AUTHENTICATED = "MoaAuthenticated"; + + public static final int SLOTIMEOUT = 30 * 1000; //30 sec + + @Autowired(required=true) protected IConfiguration authConfig; + @Autowired(required=true) private ProcessEngine processEngine; + @Autowired(required=true) private IRequestStorage requestStoreage; + @Autowired(required=true) protected IRevisionLogger revisionsLogger; + @Autowired(required=false) protected ISSOManager ssoManager; + + /* (non-Javadoc) + * @see at.gv.egiz.eaaf.core.impl.idp.auth.IAuthenticationManager#addParameterNameToWhiteList(java.lang.String) + */ + @Override + public final void addParameterNameToWhiteList(String httpReqParam) { + if (StringUtils.isNotEmpty(httpReqParam)) + reqParameterWhiteListeForModules.add(httpReqParam); + + } + + /* (non-Javadoc) + * @see at.gv.egiz.eaaf.core.impl.idp.auth.IAuthenticationManager#addHeaderNameToWhiteList(java.lang.String) + */ + @Override + public final void addHeaderNameToWhiteList(String httpReqParam) { + if (StringUtils.isNotEmpty(httpReqParam)) + reqHeaderWhiteListeForModules.add(httpReqParam.toLowerCase()); + + } + + /* (non-Javadoc) + * @see at.gv.egiz.eaaf.core.impl.idp.auth.IAuthenticationManager#addHeaderNameToWhiteList(java.lang.String) + */ + @Override + public final boolean doAuthentication(HttpServletRequest httpReq, HttpServletResponse httpResp, + IRequest pendingReq) throws EAAFException { + + if (!(pendingReq instanceof RequestImpl)) { + log.error("Requests that need authentication MUST be of type 'RequestImpl'"); + throw new RuntimeException("Requests that need authentication HAS TO BE of type 'RequestImpl'"); + + } + + //load OA configuration from pending request + ISPConfiguration oaParam = pendingReq.getServiceProviderConfiguration(); + + //set logging context and log unique OA identifier to revision log + TransactionIDUtils.setServiceProviderId(oaParam.getUniqueIdentifier()); + revisionsLogger.logEvent(pendingReq, EVENT_AUTHENTICATION_PROCESS_FOR_SP, pendingReq.getSPEntityId()); + + //generic authentication request validation + if (pendingReq.isPassiv() && pendingReq.forceAuth()) { + // conflict! + throw new NoPassivAuthenticationException(); + } + + + //check Single Sign-On functionality if SSOManager is available + boolean isValidSSOSession = false; + if (ssoManager != null) { + log.trace("SSOManager is loaded. Starting SSO session validation ... "); + //check if SSO is allowed for this service provider + ssoManager.isSSOAllowedForSP(pendingReq, httpReq); + + //check if SSO session is active and valid + isValidSSOSession = ssoManager.checkAndValidateSSOSession(pendingReq, httpReq, httpResp); + + } + + //check if session is already authenticated + //boolean isSessionAuthenticated = tryPerformAuthentication((RequestImpl) pendingReq, isValidSSOSession); + //boolean isSessionAuthenticated = isValidSSOSession && StringUtils.isNotEmpty(pendingReq.getSSOSessionIdentifier()); + + + //force new authentication authentication process + if (pendingReq.forceAuth()) { + startAuthenticationProcess(httpReq, httpResp, (RequestImpl) pendingReq); + return false; + + //perform SSO-Consents evaluation if it it required + } else if (isValidSSOSession && pendingReq.isNeedUserConsent()) { + sendSingleSignOnConsentsEvaluation(httpReq, httpResp, (RequestImpl) pendingReq); + return false; + + + } else if (pendingReq.isPassiv()) { + if (isValidSSOSession && + StringUtils.isNotEmpty(pendingReq.getSSOSessionIdentifier()) ) { + // Passive authentication ok! --> Populate pending request from SSO session + ssoManager.populatePendingRequestWithSSOInformation(pendingReq); + revisionsLogger.logEvent(pendingReq, EVENT_AUTHENTICATION_PROCESS_FINISHED); + return true; + + } else { + throw new NoPassivAuthenticationException(); + + } + + } else { + if (isValidSSOSession && + StringUtils.isNotEmpty(pendingReq.getSSOSessionIdentifier())) { + // Is authenticated .. proceed + ssoManager.populatePendingRequestWithSSOInformation(pendingReq); + revisionsLogger.logEvent(pendingReq, EVENT_AUTHENTICATION_PROCESS_FINISHED); + return true; + + } else { + // Start authentication! + startAuthenticationProcess(httpReq, httpResp, (RequestImpl) pendingReq); + return false; + + } + } + } + + public final void performOnlyIDPLogOut(HttpServletRequest request, HttpServletResponse response, IRequest pendingReq) { + + log.debug("Close session. Remove pending request ... "); + requestStoreage.removePendingRequest(pendingReq.getPendingRequestId()); + + + if (ssoManager != null) { + try { + log.trace("'SSOManager' active. Search for active SSO sessions ... "); + if (ssoManager.destroySSOSessionOnIDPOnly(request, response, pendingReq)) + log.info("SSO session successfully closed"); + else + log.info("Closing SSO session NOT successfully"); + + } catch (EAAFSSOException e) { + log.warn("Destroying of SSO session FAILED. Reason: " + e.getMessage(), e); + + } + + } + + } + + /** + * Populate process execution context and start process engine + * + * @param httpReq + * @param httpResp + * @param pendingReq + * @throws ServletException + * @throws IOException + * @throws EAAFException + */ + private void startAuthenticationProcess(HttpServletRequest httpReq, + HttpServletResponse httpResp, RequestImpl pendingReq) + throws EAAFException { + + log.info("Starting authentication ..."); + revisionsLogger.logEvent(pendingReq, EVENT_AUTHENTICATION_PROCESS_STARTED); + + //create authentication process execution context + ExecutionContext executionContext = new ExecutionContextImpl(); + + //set oaIdentifeir + executionContext.put(EAAFConstants.PROCESS_ENGINE_SERVICE_PROVIDER_ENTITYID, + pendingReq.getServiceProviderConfiguration().getUniqueIdentifier()); + + //add X509 SSL client certificate if exist + if (httpReq.getAttribute("javax.servlet.request.X509Certificate") != null) { + log.debug("Find SSL-client-certificate on request --> Add it to context"); + executionContext.put(EAAFConstants.PROCESS_ENGINE_SSL_CLIENT_CERTIFICATE, + ((X509Certificate[])httpReq.getAttribute("javax.servlet.request.X509Certificate"))); + pendingReq.setGenericDataToSession(EAAFConstants.PROCESS_ENGINE_SSL_CLIENT_CERTIFICATE, + ((X509Certificate[])httpReq.getAttribute("javax.servlet.request.X509Certificate"))); + + } + + //add additional http request parameter to context + if (!reqParameterWhiteListeForModules.isEmpty()) { + Enumeration reqParamNames = httpReq.getParameterNames(); + while(reqParamNames.hasMoreElements()) { + String paramName = reqParamNames.nextElement(); + if (StringUtils.isNotEmpty(paramName) && reqParameterWhiteListeForModules.contains(paramName) ) + executionContext.put(paramName, StringEscapeUtils.escapeHtml4(httpReq.getParameter(paramName))); + } + } + + //add additional http request parameter to context + if (!reqHeaderWhiteListeForModules.isEmpty()) { + Enumeration reqHeaderNames = httpReq.getHeaderNames(); + while(reqHeaderNames.hasMoreElements()) { + String paramName = reqHeaderNames.nextElement(); + if (StringUtils.isNotEmpty(paramName) && reqHeaderWhiteListeForModules.contains(paramName.toLowerCase()) ) + executionContext.put(paramName, StringEscapeUtils.escapeHtml4(httpReq.getHeader(paramName))); + + } + } + + //populate more IDP specific information to execution context + populateExecutionContext(executionContext, pendingReq, httpReq); + + //start process engine + startProcessEngine(pendingReq, executionContext); + + } + + /** + * + * + * @throws EAAFException + */ + abstract protected void populateExecutionContext(ExecutionContext executionContext, + RequestImpl pendingReq, HttpServletRequest httpReq) throws EAAFException; + + /** + * Starting a user consent evaluation + * + * @param request + * @param response + * @param pendingReq + * @throws ServletException + * @throws IOException + * @throws EAAFException + */ + private void sendSingleSignOnConsentsEvaluation(HttpServletRequest request, + HttpServletResponse response, RequestImpl pendingReq) + throws EAAFException { + + log.debug("Starting SSO user-consents evaluation ..."); + + //set authenticated flag to false, because user consents is required + pendingReq.setAuthenticated(false); + + //create execution context + ExecutionContext executionContext = new ExecutionContextImpl(); + executionContext.put(ISSOManager.PROCESS_ENGINE_SSO_CONSENTS_EVALUATION, true); + + //start process engine + startProcessEngine(pendingReq, executionContext); + + } + + + /** + * Select a specific process and starting process engine + * + * @param pendingReq + * @param executionContext + * @throws EAAFException + */ + private void startProcessEngine(RequestImpl pendingReq, ExecutionContext executionContext) throws EAAFException { + try { + //put pending-request ID on execurtionContext + executionContext.put(EAAFConstants.PROCESS_ENGINE_PENDINGREQUESTID, pendingReq.getPendingRequestId()); + + // create process instance + String processDefinitionId = ModuleRegistration.getInstance().selectProcess(executionContext); + + if (processDefinitionId == null) { + log.warn("No suitable process found for PendingReqId " + pendingReq.getPendingRequestId() ); + throw new EAAFException( + "process.02", + new Object[] {pendingReq.getPendingRequestId()} + ,"No suitable process found for PendingReqId " + pendingReq.getPendingRequestId()); + + } + + String processInstanceId = processEngine.createProcessInstance(processDefinitionId, executionContext); + + // keep process instance id in protocol pending-request + pendingReq.setProcessInstanceId(processInstanceId); + + //store pending-request + requestStoreage.storePendingRequest(pendingReq); + + // start process + processEngine.start(pendingReq); + + } catch (ProcessExecutionException e) { + Throwable cause = e.getCause(); + if (cause != null && cause instanceof TaskExecutionException) { + Throwable taskCause = cause.getCause(); + if (taskCause != null && taskCause instanceof EAAFException) { + EAAFException moaTaskCause = (EAAFException) taskCause; + log.warn(taskCause.getMessage(), taskCause); + throw moaTaskCause; + + } + } + + throw new EAAFException( + "process.01", + new Object[] { pendingReq.getProcessInstanceId(), pendingReq.getPendingRequestId() }, + "Authentication process execution FAILED", + e); + } + + } +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/RequestStorage.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/RequestStorage.java new file mode 100644 index 00000000..7e0d4cc7 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/RequestStorage.java @@ -0,0 +1,119 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.core.impl.idp.auth; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.IRequestStorage; +import at.gv.egiz.eaaf.core.api.idp.process.ProcessInstanceStoreDAO; +import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage; +import at.gv.egiz.eaaf.core.exceptions.EAAFException; +import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException; +import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl; +import at.gv.egiz.eaaf.core.impl.utils.Random; +import at.gv.egiz.eaaf.core.impl.utils.TransactionIDUtils; + +@Service("RequestStorage") +public class RequestStorage implements IRequestStorage{ + private static final Logger log = LoggerFactory.getLogger(RequestStorage.class); + + @Autowired ITransactionStorage transactionStorage; + @Autowired ProcessInstanceStoreDAO processInstanceStore; + + @Override + public IRequest getPendingRequest(String pendingReqID) { + + try { + IRequest pendingRequest = transactionStorage.get(pendingReqID, IRequest.class); + if (pendingRequest == null) { + log.info("No PendingRequst found with pendingRequestID " + pendingReqID); + return null; + + } + + //set transactionID and sessionID to Logger + TransactionIDUtils.setAllLoggingVariables(pendingRequest); + + return pendingRequest; + + } catch (EAAFException | NullPointerException e) { + log.info("No PendingRequst found with pendingRequestID " + pendingReqID); + return null; + + } + } + + @Override + public void storePendingRequest(IRequest pendingRequest) throws EAAFException { + try { + if (pendingRequest instanceof IRequest) + transactionStorage.put(((IRequest)pendingRequest).getPendingRequestId(), pendingRequest, -1); + + else + throw new EAAFException("PendigRequest is NOT of type 'IRequest'", null); + + + } catch (EAAFException e) { + log.warn("PendingRequest with ID=" + ((IRequest)pendingRequest).getPendingRequestId() + + " can not stored.", e); + throw new EAAFStorageException("PendingRequest with Id: " + ((IRequest)pendingRequest).getPendingRequestId() + + " can not be stored", e); + + } + + } + + @Override + public void removePendingRequest(String requestID) { + + if (requestID != null) { + + //remove process-management execution instance + try { + IRequest pendingReq = getPendingRequest(requestID); + + if (pendingReq != null && + pendingReq.getProcessInstanceId() != null) + processInstanceStore.remove(pendingReq.getProcessInstanceId()); + + } catch (EAAFException e) { + log.warn("Removing process associated with pending-request:" + requestID + " FAILED.", e); + + } + + transactionStorage.remove(requestID); + + } + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.storage.IRequestStorage#changePendingRequestID(at.gv.egovernment.moa.id.moduls.IRequest) + */ + @Override + public String changePendingRequestID(IRequest pendingRequest) throws EAAFException { + + if (pendingRequest instanceof RequestImpl) { + String newRequestID = Random.nextHexRandom32(); + String oldRequestID = pendingRequest.getPendingRequestId(); + + log.debug("Change pendingRequestID from " + pendingRequest.getPendingRequestId() + + " to " + newRequestID); + + ((RequestImpl)pendingRequest).setPendingRequestId(newRequestID); + transactionStorage.changeKey(oldRequestID, newRequestID, pendingRequest); + + //only delete oldRequestID, no change. + + return newRequestID; + + } else { + log.error("PendingRequest object is not of type 'RequestImpl.class'"); + throw new EAAFException("PendingRequest object is not of type 'RequestImpl.class'", null); + } + + } +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/AbstractAuthenticationDataBuilder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/AbstractAuthenticationDataBuilder.java new file mode 100644 index 00000000..561f6f32 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/AbstractAuthenticationDataBuilder.java @@ -0,0 +1,467 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.core.impl.idp.auth.builder; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.Base64Utils; +import org.w3c.dom.DOMException; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +import at.gv.egiz.eaaf.core.api.data.PVPAttributeDefinitions; +import at.gv.egiz.eaaf.core.api.idp.IAuthenticationDataBuilder; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; +import at.gv.egiz.eaaf.core.api.idp.auth.data.IAuthProcessDataContainer; +import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink; +import at.gv.egiz.eaaf.core.exceptions.EAAFBuilderException; +import at.gv.egiz.eaaf.core.exceptions.EAAFConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EAAFParserException; +import at.gv.egiz.eaaf.core.exceptions.XPathException; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.idp.AuthenticationData; +import at.gv.egiz.eaaf.core.impl.idp.auth.data.SimpleIdentityLinkAssertionParser; +import at.gv.egiz.eaaf.core.impl.utils.XPathUtils; + + +public abstract class AbstractAuthenticationDataBuilder implements IAuthenticationDataBuilder { + private static final Logger log = LoggerFactory.getLogger(AbstractAuthenticationDataBuilder.class); + protected Collection includedToGenericAuthData = null; + @Autowired protected IConfiguration basicConfig; + + protected void generateBasicAuthData(AuthenticationData authData, IRequest pendingReq, + IAuthProcessDataContainer authProcessData) throws EAAFBuilderException, EAAFConfigurationException, XPathException, DOMException, EAAFParserException { + + if (authProcessData.getGenericSessionDataStorage() != null && + !authProcessData.getGenericSessionDataStorage().isEmpty()) + includedToGenericAuthData = authProcessData.getGenericSessionDataStorage().keySet(); + else + includedToGenericAuthData = new ArrayList(); + + //#################################################### + //set general authData info's + authData.setAuthenticationIssuer(pendingReq.getAuthURL()); + authData.setSsoSession(pendingReq.needSingleSignOnFunctionality()); + authData.setBaseIDTransferRestrication(pendingReq.getServiceProviderConfiguration().hasBaseIdTransferRestriction()); + + + //#################################################### + //parse user info's from identityLink + IIdentityLink idlFromPVPAttr = null; + IIdentityLink identityLink = authProcessData.getIdentityLink(); + if (identityLink != null) { + parseBasicUserInfosFromIDL(authData, identityLink, includedToGenericAuthData); + + } else { + // identityLink is not direct in MOASession + String pvpAttrIDL = authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_IDENTITY_LINK_NAME, String.class); + //find PVP-Attr. which contains the IdentityLink + if (StringUtils.isNotEmpty(pvpAttrIDL)) { + log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_IDENTITY_LINK_FRIENDLY_NAME + + " --> Parse basic user info's from that attribute."); + InputStream idlStream = null; + try { + idlStream = new ByteArrayInputStream(Base64Utils.decodeFromString(pvpAttrIDL)); + idlFromPVPAttr = new SimpleIdentityLinkAssertionParser(idlStream).parseIdentityLink(); + parseBasicUserInfosFromIDL(authData, idlFromPVPAttr, includedToGenericAuthData); + + //set identitylink into AuthProcessData + authProcessData.setIdentityLink(idlFromPVPAttr);; + + } catch (EAAFParserException e) { + log.warn("Received IdentityLink is not valid", e); + + } catch (Exception e) { + log.warn("Received IdentityLink is not valid", e); + + } finally { + try { + includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_IDENTITY_LINK_NAME); + if (idlStream != null) + idlStream.close(); + + } catch (IOException e) { + log.warn("Close InputStream FAILED.", e); + + } + } + } + + //if no basic user info's are set yet, parse info's single PVP-Attributes + if (StringUtils.isEmpty(authData.getFamilyName())) { + log.debug("No IdentityLink found or not parseable --> Parse basic user info's from single PVP-Attributes."); + authData.setFamilyName(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.PRINCIPAL_NAME_NAME, String.class)); + authData.setGivenName(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.GIVEN_NAME_NAME, String.class)); + authData.setDateOfBirth(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.BIRTHDATE_NAME, String.class)); + authData.setIdentificationValue(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_SOURCE_PIN_NAME, String.class)); + authData.setIdentificationType(authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_SOURCE_PIN_TYPE_NAME, String.class)); + + //remove corresponding keys from genericSessionData if exists + includedToGenericAuthData.remove(PVPAttributeDefinitions.PRINCIPAL_NAME_NAME); + includedToGenericAuthData.remove(PVPAttributeDefinitions.GIVEN_NAME_NAME); + includedToGenericAuthData.remove(PVPAttributeDefinitions.BIRTHDATE_NAME); + includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_NAME); + includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_TYPE_NAME); + } + + } + + if (authData.getIdentificationType() != null && + !authData.getIdentificationType().equals(EAAFConstants.URN_PREFIX_BASEID)) { + log.trace("IdentificationType is not a baseID --> clear it. "); + authData.setBPK(authData.getIdentificationValue()); + authData.setBPKType(authData.getIdentificationType()); + + authData.setIdentificationValue(null); + authData.setIdentificationType(null); + } + + //#################################################### + //set QAA level + includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_NAME); + String currentLoA = null; + if (StringUtils.isNotEmpty(authProcessData.getQAALevel())) + currentLoA = authProcessData.getQAALevel(); + else { + currentLoA = authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_NAME, String.class); + if (StringUtils.isNotEmpty(currentLoA)) { + log.debug("Find PVP-Attr '" + PVPAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_FRIENDLY_NAME + "':" + currentLoA + + " --> Parse QAA-Level from that attribute."); + + } + } + if (StringUtils.isNotEmpty(currentLoA)) { + if (currentLoA.startsWith(EAAFConstants.EIDAS_QAA_PREFIX)) { + authData.seteIDASLoA(currentLoA); + + } else + log.info("Only eIDAS LoAs are supported by this implementation"); + + } else { + log.info("No QAA level found. Set to default level " + EAAFConstants.EIDAS_QAA_LOW); + authData.seteIDASLoA(EAAFConstants.EIDAS_QAA_LOW); + + } + + //#################################################### + //set isForeigner flag + //TODO: change to new eIDAS-token attribute identifier + if (authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_STORK_TOKEN_NAME) != null) { + log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_STORK_TOKEN_FRIENDLY_NAME + + " --> Set 'isForeigner' flag to TRUE"); + authData.setForeigner(true); + + } else { + authData.setForeigner(authProcessData.isForeigner()); + + } + + //#################################################### + //set citizen country-code + includedToGenericAuthData.remove(PVPAttributeDefinitions.EID_ISSUING_NATION_NAME); + String pvpCCCAttr = authProcessData.getGenericDataFromSession(PVPAttributeDefinitions.EID_ISSUING_NATION_NAME, String.class); + if (StringUtils.isNotEmpty(pvpCCCAttr)) { + authData.setCiticenCountryCode(pvpCCCAttr); + log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_ISSUING_NATION_FRIENDLY_NAME); + + } else { + if (authData.isForeigner()) { + //TODO!!!! + + } else { + authData.setCiticenCountryCode(basicConfig.getBasicConfiguration( + IConfiguration.CONFIG_PROPS_AUTH_DEFAULT_COUNTRYCODE, + EAAFConstants.COUNTRYCODE_AUSTRIA)); + + } + } + + + //#################################################### + // set bPK and IdentityLink + String pvpbPKValue = getbPKValueFromPVPAttribute(authProcessData); + String pvpbPKTypeAttr = getbPKTypeFromPVPAttribute(authProcessData); + Pair pvpEncbPKAttr = getEncryptedbPKFromPVPAttribute(authProcessData, authData, pendingReq.getServiceProviderConfiguration()); + + //check if a unique ID for this citizen exists + if (StringUtils.isEmpty(authData.getIdentificationValue()) && + StringUtils.isEmpty(pvpbPKValue) && StringUtils.isEmpty(authData.getBPK()) && + pvpEncbPKAttr == null) { + log.info("Can not build authData, because moaSession include no bPK, encrypted bPK or baseID"); + throw new EAAFBuilderException("builder.08", new Object[]{"No " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME}, + "No " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME); + + } + + // baseID is in MOASesson --> calculate bPK directly + if (StringUtils.isNotEmpty(authData.getIdentificationValue())) { + log.debug("Citizen baseID is in MOASession --> calculate bPK from this."); + Pair result = buildOAspecificbPK(pendingReq, authData); + authData.setBPK(result.getFirst()); + authData.setBPKType(result.getSecond()); + + //check if bPK already added to AuthData matches OA + } else if (StringUtils.isNotEmpty(authData.getBPK()) + && matchsReceivedbPKToOnlineApplication(pendingReq.getServiceProviderConfiguration(), authData.getBPKType()) ) { + log.debug("Correct bPK is already included in AuthData."); + + //check if bPK received by PVP-Attribute matches OA + } else if (StringUtils.isNotEmpty(pvpbPKValue) && + matchsReceivedbPKToOnlineApplication(pendingReq.getServiceProviderConfiguration(), pvpbPKTypeAttr)) { + log.debug("Receive correct bPK from PVP-Attribute"); + authData.setBPK(pvpbPKValue); + authData.setBPKType(pvpbPKTypeAttr); + + //check if decrypted bPK exists + } else if (pvpEncbPKAttr != null) { + log.debug("Receive bPK as encrypted bPK and decryption was possible."); + authData.setBPK(pvpEncbPKAttr.getFirst()); + authData.setBPKType(pvpEncbPKAttr.getSecond()); + + //ask SZR to get bPK + } else { + String notValidbPK = authData.getBPK(); + String notValidbPKType = authData.getBPKType(); + if (StringUtils.isEmpty(notValidbPK) && + StringUtils.isEmpty(notValidbPKType)) { + notValidbPK = pvpbPKValue; + notValidbPKType = pvpbPKTypeAttr; + + if (StringUtils.isEmpty(notValidbPK) && + StringUtils.isEmpty(notValidbPKType)) { + log.error("No bPK in MOASession. THIS error should not occur any more."); + throw new NullPointerException("No bPK in MOASession. THIS error should not occur any more."); + } + } + + Pair baseIDFromSZR = getbaseIDFromSZR(authData, notValidbPK, notValidbPKType); + if (baseIDFromSZR != null) { + log.info("Receive citizen baseID from SRZ. Authentication can be completed"); + authData.setIdentificationValue(baseIDFromSZR.getFirst()); + authData.setIdentificationType(baseIDFromSZR.getSecond()); + Pair result = buildOAspecificbPK(pendingReq, authData); + authData.setBPK(result.getFirst()); + authData.setBPKType(result.getSecond()); + + } else { + log.warn("Can not build authData, because moaSession include no valid bPK, encrypted bPK or baseID"); + throw new EAAFBuilderException("builder.08", new Object[]{"No valid " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME}, + "No valid " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.EID_SOURCE_PIN_FRIENDLY_NAME + + " or " + PVPAttributeDefinitions.ENC_BPK_LIST_FRIENDLY_NAME); + + } + } + + //build IdentityLink + if (authProcessData.getIdentityLink() != null) + authData.setIdentityLink(buildOAspecificIdentityLink( + pendingReq.getServiceProviderConfiguration(), + authProcessData.getIdentityLink(), + authData.getBPK(), + authData.getBPKType())); + else + log.info("Can NOT set IdentityLink. Msg: No IdentityLink found"); + + } + + //extract a encrypted bPK from PVP attrobute + protected abstract Pair getEncryptedbPKFromPVPAttribute(IAuthProcessDataContainer authProcessDataContainer, + AuthenticationData authData, ISPConfiguration spConfig) throws EAAFBuilderException; + + //request baseId from SRZ + protected abstract Pair getbaseIDFromSZR(AuthenticationData authData, String notValidbPK, + String notValidbPKType); + + + protected Pair buildOAspecificbPK(IRequest pendingReq, AuthenticationData authData) throws EAAFBuilderException { + ISPConfiguration oaParam = pendingReq.getServiceProviderConfiguration(); + + String baseID = authData.getIdentificationValue(); + String baseIDType = authData.getIdentificationType(); + Pair sectorSpecId = null; + + if (EAAFConstants.URN_PREFIX_BASEID.equals(baseIDType)) { + //SAML1 legacy target parameter work-around + String spTargetId = oaParam.getAreaSpecificTargetIdentifier(); + log.debug("Use OA target identifier '" + spTargetId + "' from configuration"); + + //calculate sector specific unique identifier + sectorSpecId = new BPKBuilder().generateAreaSpecificPersonIdentifier(baseID, spTargetId); + + } else { + log.error("!!!baseID-element does not include a baseID. This should not be happen any more!!!"); + sectorSpecId = Pair.newInstance(baseID, baseIDType); + + } + + log.trace("Authenticate user with bPK:" + sectorSpecId.getFirst() + " Type:" + sectorSpecId.getSecond()); + return sectorSpecId; + + } + + protected IIdentityLink buildOAspecificIdentityLink(ISPConfiguration spConfig, IIdentityLink idl, String bPK, String bPKType) throws EAAFConfigurationException, XPathException, DOMException, EAAFParserException { + if (spConfig.hasBaseIdTransferRestriction()) { + log.debug("SP: " + spConfig.getUniqueIdentifier() + " has baseId transfer restriction. Remove baseId from IDL ..."); + Element idlassertion = idl.getSamlAssertion(); + //set bpk/wpbk; + Node prIdentification = XPathUtils.selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_IDENT_VALUE_XPATH); + prIdentification.getFirstChild().setNodeValue(bPK); + //set bkp/wpbk type + Node prIdentificationType = XPathUtils.selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_IDENT_TYPE_XPATH); + prIdentificationType.getFirstChild().setNodeValue(bPKType); + + SimpleIdentityLinkAssertionParser idlparser = new SimpleIdentityLinkAssertionParser(idlassertion); + return idlparser.parseIdentityLink(); + + } else + return idl; + + } + + /** + * Check a bPK-Type against a Service-Provider configuration
+ * If bPK-Type is null the result is false. + * + * @param oaParam Service-Provider configuration, never null + * @param bPKType bPK-Type to check + * @return true, if bPK-Type matchs to Service-Provider configuration, otherwise false + */ + private boolean matchsReceivedbPKToOnlineApplication(ISPConfiguration oaParam, String bPKType) { + return oaParam.getAreaSpecificTargetIdentifier().equals(bPKType); + + } + + /** + * Parse information from an IdentityLink into AuthData object + * + * @param authData + * @param identityLink + * @param includedGenericSessionData + */ + private void parseBasicUserInfosFromIDL(AuthenticationData authData, IIdentityLink identityLink, Collection includedGenericSessionData) { + authData.setIdentificationValue(identityLink.getIdentificationValue()); + authData.setIdentificationType(identityLink.getIdentificationType()); + + authData.setGivenName(identityLink.getGivenName()); + authData.setFamilyName(identityLink.getFamilyName()); + authData.setDateOfBirth(identityLink.getDateOfBirth()); + + //remove corresponding keys from genericSessionData if exists + includedGenericSessionData.remove(PVPAttributeDefinitions.PRINCIPAL_NAME_NAME); + includedGenericSessionData.remove(PVPAttributeDefinitions.GIVEN_NAME_NAME); + includedGenericSessionData.remove(PVPAttributeDefinitions.BIRTHDATE_NAME); + includedGenericSessionData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_NAME); + includedGenericSessionData.remove(PVPAttributeDefinitions.EID_SOURCE_PIN_TYPE_NAME); + + } + + /** + * Get bPK from PVP Attribute 'BPK_NAME', which could be exist in + * MOASession as 'GenericData'
session.getGenericDataFromSession(PVPConstants.BPK_NAME, String.class)
+ * + * @param session MOASession, but never null + * @return bPK, which was received by PVP-Attribute, or null if no attribute exists + */ + private String getbPKValueFromPVPAttribute(IAuthProcessDataContainer session) { + String pvpbPKValueAttr = session.getGenericDataFromSession(PVPAttributeDefinitions.BPK_NAME, String.class); + if (StringUtils.isNotEmpty(pvpbPKValueAttr)) { + + //fix a wrong bPK-value prefix, which was used in some PVP Standardportal implementations + if (pvpbPKValueAttr.startsWith("bPK:")) { + log.warn("Attribute " + PVPAttributeDefinitions.BPK_NAME + + " contains a not standardize prefix! Staring attribute value correction process ..."); + pvpbPKValueAttr = pvpbPKValueAttr.substring("bPK:".length()); + + } + + String[] spitted = pvpbPKValueAttr.split(":"); + if (spitted.length != 2) { + log.warn("Attribute " + PVPAttributeDefinitions.BPK_NAME + " has a wrong encoding and can NOT be USED!" + + " Value:" + pvpbPKValueAttr); + return null; + + } + log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.BPK_FRIENDLY_NAME); + return spitted[1]; + + } + + return null; + } + + /** + * Get bPK-Type from PVP Attribute 'EID_SECTOR_FOR_IDENTIFIER_NAME', which could be exist in + * MOASession as 'GenericData'
session.getGenericDataFromSession(PVPConstants.EID_SECTOR_FOR_IDENTIFIER_NAME, String.class)
+ * + * @param session MOASession, but never null + * @return bPKType, which was received by PVP-Attribute, or null if no attribute exists + */ + private String getbPKTypeFromPVPAttribute(IAuthProcessDataContainer session) { + String pvpbPKTypeAttr = session.getGenericDataFromSession(PVPAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME, String.class); + if (StringUtils.isNotEmpty(pvpbPKTypeAttr)) { + + //fix a wrong bPK-Type encoding, which was used in some PVP Standardportal implementations + if (pvpbPKTypeAttr.startsWith(EAAFConstants.URN_PREFIX_CDID) && + !pvpbPKTypeAttr.substring(EAAFConstants.URN_PREFIX_CDID.length(), + EAAFConstants.URN_PREFIX_CDID.length() + 1).equals("+")) { + log.warn("Receive uncorrect encoded bBKType attribute " + pvpbPKTypeAttr + " Starting attribute value correction ... "); + pvpbPKTypeAttr = EAAFConstants.URN_PREFIX_CDID + "+" + pvpbPKTypeAttr.substring(EAAFConstants.URN_PREFIX_CDID.length() + 1); + + } + log.debug("Find PVP-Attr: " + PVPAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_FRIENDLY_NAME); + return pvpbPKTypeAttr; + } + + return null; + + + /* + * INFO: This code could be used to extract the bPKType from 'PVPConstants.BPK_NAME', + * because the prefix of BPK_NAME attribute contains the postfix of the bPKType + * + * Now, all PVP Standardportals should be able to send 'EID_SECTOR_FOR_IDENTIFIER' + * PVP attributes + */ +// String pvpbPKValueAttr = session.getGenericDataFromSession(PVPConstants.BPK_NAME, String.class); +// String[] spitted = pvpbPKValueAttr.split(":"); +// if (MiscUtil.isEmpty(authData.getBPKType())) { +// Logger.debug("PVP assertion contains NO bPK/wbPK target attribute. " + +// "Starting target extraction from bPK/wbPK prefix ..."); +// //exract bPK/wbPK type from bpk attribute value prefix if type is +// //not transmitted as single attribute +// Pattern pattern = Pattern.compile("[a-zA-Z]{2}(-[a-zA-Z]+)?"); +// Matcher matcher = pattern.matcher(spitted[0]); +// if (matcher.matches()) { +// //find public service bPK +// authData.setBPKType(Constants.URN_PREFIX_CDID + "+" + spitted[0]); +// Logger.debug("Found bPK prefix. Set target to " + authData.getBPKType()); +// +// } else { +// //find business service wbPK +// authData.setBPKType(Constants.URN_PREFIX_WBPK+ "+" + spitted[0]); +// Logger.debug("Found wbPK prefix. Set target to " + authData.getBPKType()); +// +// } +// } + + } + +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/BPKBuilder.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/BPKBuilder.java new file mode 100644 index 00000000..62a57dd1 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/builder/BPKBuilder.java @@ -0,0 +1,302 @@ +/******************************************************************************* + * Copyright 2014 Federal Chancellery Austria + * MOA-ID has been developed in a cooperation between BRZ, the Federal + * Chancellery Austria - ICT staff unit, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * http://www.osor.eu/eupl/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +/* + * Copyright 2003 Federal Chancellery Austria + * MOA-ID has been developed in a cooperation between BRZ, the Federal + * Chancellery Austria - ICT staff unit, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * http://www.osor.eu/eupl/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + + +package at.gv.egiz.eaaf.core.impl.idp.auth.builder; + +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Base64Utils; + +import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +import at.gv.egiz.eaaf.core.exceptions.EAAFBuilderException; +import at.gv.egiz.eaaf.core.impl.data.Pair; + +/** + * Builder for the bPK, as defined in + * "Ableitung f¨r die bereichsspezifische Personenkennzeichnung" + * version 1.0.1 from "reference.e-government.gv.at". + * + */ +public class BPKBuilder { + private static final Logger log = LoggerFactory.getLogger(BPKBuilder.class); + + /** + * Calculates an area specific unique person-identifier from a baseID + * + * @param baseID baseId from user but never null + * @param targetIdentifier target identifier for area specific identifier calculation but never null + * @return Pair but never null + * @throws EAAFBuilderException if some input data are not valid + */ + public Pair generateAreaSpecificPersonIdentifier(String baseID, String targetIdentifier) throws EAAFBuilderException { + return generateAreaSpecificPersonIdentifier(baseID, EAAFConstants.URN_PREFIX_BASEID, targetIdentifier); + + } + + /** + * Calculates an area specific unique person-identifier from an unique identifier with a specific type + * + * @param baseID baseId from user but never null + * @param baseIdType Type of the baseID but never null + * @param targetIdentifier target identifier for area specific identifier calculation but never null + * @return Pair but never null + * @throws EAAFBuilderException if some input data are not valid + */ + public Pair generateAreaSpecificPersonIdentifier(String baseID, String baseIdType, String targetIdentifier) throws EAAFBuilderException{ + if (StringUtils.isEmpty(baseID)) + throw new EAAFBuilderException("builder.00", new Object[]{"baseID is empty or null"}, + "BaseId is empty or null"); + + if (StringUtils.isEmpty(baseIdType)) + throw new EAAFBuilderException("builder.00", new Object[]{"the type of baseID is empty or null"}, + "Type of baseId is empty or null"); + + if (StringUtils.isEmpty(targetIdentifier)) + throw new EAAFBuilderException("builder.00", new Object[]{"SP specific target identifier is empty or null"}, + "SP specific target identifier is empty or null"); + + if (baseIdType.equals(EAAFConstants.URN_PREFIX_BASEID)) { + log.trace("Find baseID. Starting unique identifier caluclation for this target"); + + if (targetIdentifier.startsWith(EAAFConstants.URN_PREFIX_CDID) || + targetIdentifier.startsWith(EAAFConstants.URN_PREFIX_WBPK)) { + log.trace("Calculate bPK, wbPK, or STORK identifier for target: " + targetIdentifier); + return Pair.newInstance(calculatebPKwbPK(baseID + "+" + targetIdentifier), targetIdentifier); + + } else if (targetIdentifier.startsWith(EAAFConstants.URN_PREFIX_EIDAS)) { + log.trace("Calculate eIDAS identifier for target: " + targetIdentifier); + String[] splittedTarget = targetIdentifier.split("\\+"); + String cititzenCountryCode = splittedTarget[1]; + String eIDASOutboundCountry = splittedTarget[2]; + + if (cititzenCountryCode.equalsIgnoreCase(eIDASOutboundCountry)) { + log.warn("Suspect configuration FOUND!!! CitizenCountry equals DestinationCountry"); + + } + return buildeIDASIdentifer(baseID, baseIdType, cititzenCountryCode, eIDASOutboundCountry); + + + } else + throw new EAAFBuilderException("builder.00", + new Object[]{"Target identifier: " + targetIdentifier + " is NOT allowed or unknown"}, + "Target identifier: " + targetIdentifier + " is NOT allowed or unknown"); + + } else { + log.trace("BaseID is not of type " + EAAFConstants.URN_PREFIX_BASEID + ". Check type against requested target ..."); + if (baseIdType.equals(targetIdentifier)) { + log.debug("Unique identifier is already area specific. Is nothing todo"); + return Pair.newInstance(baseID, targetIdentifier); + + } else { + log.warn("Get unique identifier for target: " + baseIdType + " but target: " + targetIdentifier + " is required!"); + throw new EAAFBuilderException("builder.00", + new Object[]{"Get unique identifier for target: " + baseIdType + " but target: " + targetIdentifier + " is required"}, + "Get unique identifier for target: " + baseIdType + " but target: " + targetIdentifier + " is required"); + + } + } + } + + + /** + * Builds the eIDAS from the given parameters. + * + * @param baseID baseID of the citizen + * @param baseIDType Type of the baseID + * @param sourceCountry CountryCode of that country, which build the eIDAs ID + * @param destinationCountry CountryCode of that country, which receives the eIDAs ID + * + * @return Pair in a BASE64 encoding + * @throws EAAFBuilderException if some input data are not valid + */ + private Pair buildeIDASIdentifer(String baseID, String baseIDType, String sourceCountry, String destinationCountry) + throws EAAFBuilderException { + String bPK = null; + String bPKType = null; + + // check if we have been called by public sector application + if (baseIDType.startsWith(EAAFConstants.URN_PREFIX_BASEID)) { + bPKType = EAAFConstants.URN_PREFIX_EIDAS + "+" + sourceCountry + "+" + destinationCountry; + log.debug("Building eIDAS identification from: [identValue]+" + bPKType); + bPK = calculatebPKwbPK(baseID + "+" + bPKType); + + } else { // if not, sector identification value is already calculated by BKU + log.debug("eIDAS eIdentifier already provided by BKU"); + bPK = baseID; + } + + if ((StringUtils.isEmpty(bPK) || + StringUtils.isEmpty(sourceCountry) || + StringUtils.isEmpty(destinationCountry))) { + throw new EAAFBuilderException("builder.00", + new Object[]{"eIDAS-ID", "Unvollständige Parameterangaben: identificationValue=" + + bPK + ", Zielland=" + destinationCountry + ", Ursprungsland=" + sourceCountry} + ,"eIDAS-ID: Unvollständige Parameterangaben: identificationValue=" + + bPK + ", Zielland=" + destinationCountry + ", Ursprungsland=" + sourceCountry); + } + + log.debug("Building eIDAS identification from: " + sourceCountry+"/"+destinationCountry+"/" + "[identValue]"); + String eIdentifier = sourceCountry + "/" + destinationCountry + "/" + bPK; + + return Pair.newInstance(eIdentifier, bPKType); + } + + public static String encryptBPK(String bpk, String target, PublicKey publicKey) throws EAAFBuilderException { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + if (target.startsWith(EAAFConstants.URN_PREFIX_CDID + "+")) + target = target.substring((EAAFConstants.URN_PREFIX_CDID + "+").length()); + + String input = "V1::urn:publicid:gv.at:cdid+" + target + "::" + + bpk + "::" + + sdf.format(new Date()); + System.out.println(input); + byte[] result; + try { + byte[] inputBytes = input.getBytes("ISO-8859-1"); + result = encrypt(inputBytes, publicKey); + return new String(Base64Utils.encode(result), "ISO-8859-1").replaceAll("\r\n", ""); + //return new String(Base64Utils.encode(result, "ISO-8859-1")).replaceAll("\r\n", ""); + + + } catch (Exception e) { + throw new EAAFBuilderException("bPK encryption FAILED", null, + e.getMessage(), e); + + } + } + + public static String decryptBPK(String encryptedBpk, String target, PrivateKey privateKey) throws EAAFBuilderException { + String decryptedString; + try { + //byte[] encryptedBytes = Base64Utils.decode(encryptedBpk, false, "ISO-8859-1"); + byte[] encryptedBytes = Base64Utils.decode(encryptedBpk.getBytes("ISO-8859-1")); + byte[] decryptedBytes = decrypt(encryptedBytes, privateKey); + decryptedString = new String(decryptedBytes, "ISO-8859-1"); + + } catch (Exception e) { + throw new EAAFBuilderException("bPK decryption FAILED", null, + e.getMessage(), e); + + } + + String tmp = decryptedString.substring(decryptedString.indexOf('+') + 1); + String sector = tmp.substring(0, tmp.indexOf("::")); + tmp = tmp.substring(tmp.indexOf("::") + 2); + String bPK = tmp.substring(0, tmp.indexOf("::")); + + if (target.startsWith(EAAFConstants.URN_PREFIX_CDID + "+")) + target = target.substring((EAAFConstants.URN_PREFIX_CDID + "+").length()); + + if (target.equals(sector)) + return bPK; + + else { + log.error("Decrypted bPK does not match to request bPK target."); + return null; + } + } + + private String calculatebPKwbPK(String basisbegriff) throws EAAFBuilderException { + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] hash = md.digest(basisbegriff.getBytes("ISO-8859-1")); + String hashBase64 = new String(Base64Utils.encode(hash), "ISO-8859-1").replaceAll("\r\n", ""); //Base64Utils.encode(hash); + return hashBase64; + + } catch (Exception ex) { + throw new EAAFBuilderException("builder.00", new Object[]{"bPK/wbPK", ex.toString()}, + ex.getMessage(), ex); + + } + + } + + private static byte[] encrypt(byte[] inputBytes, PublicKey publicKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { + byte[] result; + Cipher cipher = null; + try { + cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); // try with bouncycastle + + } catch(NoSuchAlgorithmException e) { + cipher = Cipher.getInstance("RSA/ECB/OAEP"); // try with iaik provider + } + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + result = cipher.doFinal(inputBytes); + + return result; + } + + private static byte[] decrypt(byte[] encryptedBytes, PrivateKey privateKey) + throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException{ + byte[] result; + Cipher cipher = null; + try { + cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); // try with bouncycastle + + } catch(NoSuchAlgorithmException e) { + cipher = Cipher.getInstance("RSA/ECB/OAEP"); // try with iaik provider + + } + cipher.init(Cipher.DECRYPT_MODE, privateKey); + result = cipher.doFinal(encryptedBytes); + return result; + + } +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/AuthProcessDataWrapper.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/AuthProcessDataWrapper.java new file mode 100644 index 00000000..3fca5300 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/AuthProcessDataWrapper.java @@ -0,0 +1,219 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.core.impl.idp.auth.data; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +import at.gv.egiz.eaaf.core.api.idp.EAAFAuthProcessDataConstants; +import at.gv.egiz.eaaf.core.api.idp.auth.data.IAuthProcessDataContainer; +import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink; +import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException; + +public class AuthProcessDataWrapper implements IAuthProcessDataContainer, EAAFAuthProcessDataConstants { + private static final Logger log = LoggerFactory.getLogger(AuthProcessDataWrapper.class); + + protected Map authProcessData; + + public AuthProcessDataWrapper(Map authProcessData) { + this.authProcessData = authProcessData; + + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#getIssueInstant() + */ + @Override + public String getIssueInstant() { + return wrapStringObject(VALUE_ISSUEINSTANT, null, String.class); + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setIssueInstant(java.lang.String) + */ + @Override + public void setIssueInstant(String issueInstant) { + authProcessData.put(VALUE_ISSUEINSTANT, issueInstant); + + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#isAuthenticated() + */ + @Override + public boolean isAuthenticated() { + return wrapStringObject(FLAG_IS_AUTHENTICATED, false, Boolean.class); + + } + + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setAuthenticated(boolean) + */ + @Override + public void setAuthenticated(boolean authenticated) { + authProcessData.put(FLAG_IS_AUTHENTICATED, authenticated); + + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#getIdentityLink() + */ + @Override + public IIdentityLink getIdentityLink() { + return wrapStringObject(VALUE_IDENTITYLINK, null, IIdentityLink.class); + + } + + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setIdentityLink(at.gv.egovernment.moa.id.auth.data.IdentityLink) + */ + @Override + public void setIdentityLink(IIdentityLink identityLink) { + authProcessData.put(VALUE_IDENTITYLINK, identityLink); + + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#isMandateUsed() + */ + @Override + public boolean isMandateUsed() { + return wrapStringObject(FLAG_USE_MANDATE, false, Boolean.class); + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setUseMandates(boolean) + */ + @Override + public void setUseMandates(boolean useMandates) { + authProcessData.put(FLAG_USE_MANDATE, useMandates); + + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#getQAALevel() + */ + @Override + public String getQAALevel() { + return wrapStringObject(VALUE_QAALEVEL, null, String.class); + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setQAALevel(java.lang.String) + */ + @Override + public void setQAALevel(String qAALevel) { + authProcessData.put(VALUE_QAALEVEL, qAALevel); + + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#isForeigner() + */ + @Override + public boolean isForeigner() { + return wrapStringObject(FLAG_IS_FOREIGNER, false, Boolean.class); + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setForeigner(boolean) + */ + @Override + public void setForeigner(boolean isForeigner) { + authProcessData.put(FLAG_IS_FOREIGNER, isForeigner); + + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#isOW() + */ + @Override + public boolean isOW() { + return wrapStringObject(FLAG_IS_ORGANWALTER, false, Boolean.class); + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setOW(boolean) + */ + @Override + public void setOW(boolean isOW) { + authProcessData.put(FLAG_IS_ORGANWALTER, isOW); + + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#getSessionCreated() + */ + @Override + public Date getSessionCreated() { + return wrapStringObject(EAAFConstants.AUTH_DATA_CREATED, null, Date.class); + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#getGenericSessionDataStorage() + */ + @Override + public Map getGenericSessionDataStorage() { + Map result = new HashMap(); + for (String el : authProcessData.keySet()) { + if (el.startsWith(GENERIC_PREFIX)) + result.put(el.substring(GENERIC_PREFIX.length()), authProcessData.get(el)); + + } + + return result; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#getGenericDataFromSession(java.lang.String) + */ + @Override + public Object getGenericDataFromSession(String key) { + return authProcessData.get(GENERIC_PREFIX + key); + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#getGenericDataFromSession(java.lang.String, java.lang.Class) + */ + @Override + public T getGenericDataFromSession(String key, Class clazz) { + return wrapStringObject(GENERIC_PREFIX + key, null, clazz); + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IAuthenticationSession#setGenericDataToSession(java.lang.String, java.lang.Object) + */ + @Override + public void setGenericDataToSession(String key, Object object) throws EAAFStorageException { + authProcessData.put(GENERIC_PREFIX + key, object); + + } + + protected T wrapStringObject(String key, Object defaultValue, Class clazz) { + if (StringUtils.isNotEmpty(key)) { + Object obj = authProcessData.get(key); + if (obj != null && clazz.isInstance(obj)) + return (T) obj; + } + + if (defaultValue == null) + return null; + + else if (clazz.isInstance(defaultValue)) + return (T)defaultValue; + + else { + log.error("DefaultValue: " + defaultValue.getClass().getName() + " is not of Type:" + clazz.getName()); + throw new IllegalStateException("DefaultValue: " + defaultValue.getClass().getName() + " is not of Type:" + clazz.getName()); + + } + } +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/IdentityLink.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/IdentityLink.java new file mode 100644 index 00000000..becd630e --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/IdentityLink.java @@ -0,0 +1,312 @@ +/******************************************************************************* + * Copyright 2014 Federal Chancellery Austria + * MOA-ID has been developed in a cooperation between BRZ, the Federal + * Chancellery Austria - ICT staff unit, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * http://www.osor.eu/eupl/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +/* + * Copyright 2003 Federal Chancellery Austria + * MOA-ID has been developed in a cooperation between BRZ, the Federal + * Chancellery Austria - ICT staff unit, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * http://www.osor.eu/eupl/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + + +package at.gv.egiz.eaaf.core.impl.idp.auth.data; + +import java.io.IOException; +import java.io.Serializable; +import java.security.PublicKey; + +import javax.xml.transform.TransformerException; + +import org.w3c.dom.Element; + +import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink; +import at.gv.egiz.eaaf.core.impl.utils.DOMUtils; + + +/** + * Data contained in an identity link issued by BMI, relevant to the MOA ID component. + *
"IdentityLink" is the translation of "Personenbindung". + * + * @author Paul Ivancsics + * @version $Id$ + */ +public class IdentityLink implements Serializable, IIdentityLink{ + + private static final long serialVersionUID = 1L; + + /** + * "identificationValue" is the translation of "Stammzahl". + */ + private String identificationValue; + /** + * "identificationType" type of the identificationValue in the IdentityLink. + */ + private String identificationType; + /** + * first name + */ + private String givenName; + /** + * family name + */ + private String familyName; + + /** + * The name as (givenName + familyName) + */ + private String name; + /** + * date of birth + */ + private String dateOfBirth; + /** + * the original saml:Assertion-Element + */ + private Element samlAssertion; + /** + * the serializes saml:Assertion + */ + private String serializedSamlAssertion; + /** + * Element /saml:Assertion/saml:AttributeStatement/saml:Subject/saml:SubjectConfirmation/saml:SubjectConfirmationData/pr:Person + */ + private Element prPerson; + /** + * we need for each dsig:Reference Element all + * transformation elements + */ + private Element[] dsigReferenceTransforms; + + /** + * The issuing time of the identity link SAML assertion. + */ + private String issueInstant; + + /** + * we need all public keys stored in + * the identity link + */ + private PublicKey[] publicKey; + + /** + * Constructor for IdentityLink + */ + public IdentityLink() { + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getDateOfBirth() + */ + @Override +public String getDateOfBirth() { + return dateOfBirth; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getFamilyName() + */ + @Override +public String getFamilyName() { + return familyName; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getGivenName() + */ + @Override +public String getGivenName() { + return givenName; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getName() + */ + @Override +public String getName() { + if (name == null) { + name = givenName + " " + familyName; + } + return name; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getIdentificationValue() + */ + @Override +public String getIdentificationValue() { + return identificationValue; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getIdentificationType() + */ + @Override + public String getIdentificationType() { + return identificationType; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setDateOfBirth(java.lang.String) + */ + @Override +public void setDateOfBirth(String dateOfBirth) { + this.dateOfBirth = dateOfBirth; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setFamilyName(java.lang.String) + */ + @Override +public void setFamilyName(String familyName) { + this.familyName = familyName; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setGivenName(java.lang.String) + */ + @Override +public void setGivenName(String givenName) { + this.givenName = givenName; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setIdentificationValue(java.lang.String) + */ + @Override +public void setIdentificationValue(String identificationValue) { + this.identificationValue = identificationValue; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setIdentificationType(java.lang.String) + */ + @Override + public void setIdentificationType(String identificationType) { + this.identificationType = identificationType; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getSamlAssertion() + */ + @Override +public Element getSamlAssertion() { + return samlAssertion; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getSerializedSamlAssertion() + */ + @Override +public String getSerializedSamlAssertion() { + return serializedSamlAssertion; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setSamlAssertion(org.w3c.dom.Element) + */ + @Override +public void setSamlAssertion(Element samlAssertion) throws TransformerException, IOException { + this.samlAssertion = samlAssertion; + this.serializedSamlAssertion = DOMUtils.serializeNode(samlAssertion); + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getDsigReferenceTransforms() + */ + @Override +public Element[] getDsigReferenceTransforms() { + return dsigReferenceTransforms; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setDsigReferenceTransforms(org.w3c.dom.Element[]) + */ + @Override +public void setDsigReferenceTransforms(Element[] dsigReferenceTransforms) { + this.dsigReferenceTransforms = dsigReferenceTransforms; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getPublicKey() + */ + @Override +public PublicKey[] getPublicKey() { + return publicKey; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setPublicKey(java.security.PublicKey[]) + */ + @Override +public void setPublicKey(PublicKey[] publicKey) { + this.publicKey = publicKey; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getPrPerson() + */ + @Override +public Element getPrPerson() { + return prPerson; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setPrPerson(org.w3c.dom.Element) + */ + @Override +public void setPrPerson(Element prPerson) { + this.prPerson = prPerson; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#getIssueInstant() + */ + @Override +public String getIssueInstant() { + return issueInstant; + } + + /* (non-Javadoc) + * @see at.gv.egovernment.moa.id.auth.data.IIdentityLink#setIssueInstant(java.lang.String) + */ + @Override +public void setIssueInstant(String issueInstant) { + this.issueInstant = issueInstant; + } + +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/SimpleIdentityLinkAssertionParser.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/SimpleIdentityLinkAssertionParser.java new file mode 100644 index 00000000..7fb5e642 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/data/SimpleIdentityLinkAssertionParser.java @@ -0,0 +1,326 @@ +/******************************************************************************* + * Copyright 2014 Federal Chancellery Austria + * MOA-ID has been developed in a cooperation between BRZ, the Federal + * Chancellery Austria - ICT staff unit, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * http://www.osor.eu/eupl/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + ******************************************************************************/ +/* + * Copyright 2003 Federal Chancellery Austria + * MOA-ID has been developed in a cooperation between BRZ, the Federal + * Chancellery Austria - ICT staff unit, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * http://www.osor.eu/eupl/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + + +package at.gv.egiz.eaaf.core.impl.idp.auth.data; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.util.Base64Utils; +import org.w3c.dom.Element; +import org.w3c.dom.traversal.NodeIterator; + +import at.gv.egiz.eaaf.core.api.data.XMLNamespaceConstants; +import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink; +import at.gv.egiz.eaaf.core.exceptions.EAAFParserException; +import at.gv.egiz.eaaf.core.impl.utils.DOMUtils; +import at.gv.egiz.eaaf.core.impl.utils.XPathUtils; + +/** + * Parses MDS from an identity link <saml:Assertion> + *
+ * This IDL parser extract NO key information! + + */ +public class SimpleIdentityLinkAssertionParser { + + // + // XPath namespace prefix shortcuts + // + + /** Xpath prefix for reaching PersonData Namespaces */ + private static final String PDATA = XMLNamespaceConstants.PD_PREFIX + ":"; + /** Xpath prefix for reaching SAML Namespaces */ + private static final String SAML = XMLNamespaceConstants.SAML_PREFIX + ":"; + /** Xpath prefix for reaching XML-DSIG Namespaces */ + private static final String DSIG = XMLNamespaceConstants.DSIG_PREFIX + ":"; + /** Xpath prefix for reaching ECDS Namespaces */ + private static final String ECDSA = XMLNamespaceConstants.ECDSA_PREFIX + ":"; + /** Xpath expression to the root element */ + private static final String ROOT = ""; + /** Xpath expression to the SAMLSubjectConfirmationData element */ + private static final String SAML_SUBJECT_CONFIRMATION_DATA_XPATH = + ROOT + + SAML + + "AttributeStatement/" + + SAML + + "Subject/" + + SAML + + "SubjectConfirmation/" + + SAML + + "SubjectConfirmationData"; + /** Xpath expression to the PersonData element */ + private static final String PERSON_XPATH = + SAML_SUBJECT_CONFIRMATION_DATA_XPATH + + "/" + + PDATA + + "Person"; + /** Xpath expression to the PersonData GivenName element */ + public static final String PERSON_GIVEN_NAME_XPATH = + PERSON_XPATH + + "/" + + PDATA + + "Name/" + + PDATA + + "GivenName"; + /** Xpath expression to the PersonData FamilyName element */ + public static final String PERSON_FAMILY_NAME_XPATH = + PERSON_XPATH + + "/" + + PDATA + + "Name/" + + PDATA + + "FamilyName"; + /** Xpath expression to the PersonData DateOfBirth element */ + public static final String PERSON_DATE_OF_BIRTH_XPATH = + PERSON_XPATH + + "/" + + PDATA + + "DateOfBirth"; + /** Xpath expression to the Identification element */ + private static final String PERSON_IDENT_XPATH = + PERSON_XPATH + + "/" + + PDATA + + "Identification"; + + /** Xpath expression to the Identification Value element */ + public static final String PERSON_IDENT_VALUE_XPATH = + PERSON_XPATH + + "/" + + PDATA + + "Identification/" + + PDATA + + "Value"; + + /** Xpath expression to the Identification Value element */ + public static final String PERSON_IDENT_TYPE_XPATH = + PERSON_XPATH + + "/" + + PDATA + + "Identification/" + + PDATA + + "Type"; + + /** Xpath expression to the RSAKeyValue element */ + private static final String RSA_KEY_VALUE_XPATH = + ROOT + + SAML + + "AttributeStatement/" + + SAML + + "Attribute/" + + SAML + + "AttributeValue/" + + DSIG + + "RSAKeyValue"; + + /** Xpath expression to the ECKeyValue element */ + private static final String ECDSA_KEY_VALUE_XPATH = + ROOT + + SAML + + "AttributeStatement/" + + SAML + + "Attribute/" + + SAML + + "AttributeValue/" + + ECDSA + + "ECDSAKeyValue"; + + + /** Xpath expression to the RSA Modulus element */ + private static final String RSA_KEY_MODULUS_XPATH = DSIG + "Modulus"; + /** Xpath expression to the RSA Exponent element */ + private static final String RSA_KEY_EXPONENT_XPATH = DSIG + "Exponent"; + /** Xpath expression to the DSIG X509Certificate element */ + private static final String DSIG_CERTIFICATES_XPATH = + ROOT + + DSIG + + "Signature/" + + DSIG + + "KeyInfo/" + + DSIG + + "X509Data/" + + DSIG + + "X509Certificate"; + /** Xpath expression to the DSIG Transforms element */ + private static final String DSIG_REFERENCE_TRANSFORMATION_XPATH = + ROOT + + DSIG + + "Signature/" + + DSIG + + "SignedInfo/" + + DSIG + + "Reference/" + + DSIG + + "Transforms"; + + /** The IssueInstant attribute of the SAML assertion */ + private static final String ISSUE_INSTANT_ATTR = "IssueInstant"; + + /**This is the root element of the XML-Document provided by the Security Layer Card*/ + private Element assertionElem; + + /** + * Constructor for IdentityLinkAssertionParser. + * A DOM-representation of the incoming String will be created + * @param xmlAssertion <saml:Assertion> as String + * @throws EAAFParserException on any parsing error + */ + public SimpleIdentityLinkAssertionParser(String xmlAssertion) throws EAAFParserException { + try { + InputStream s = new ByteArrayInputStream(xmlAssertion.getBytes("UTF-8")); + assertionElem = DOMUtils.parseXmlValidating(s); + + } + catch (Throwable t) { + throw new EAAFParserException("parser.01", new Object[] { t.toString()}, + t.getMessage(), t); + + } + } + + /** + * Sets the <@link assertionElem>. + * @param xmlAssertion the assertion element + * @throws EAAFParserException on any parsing error + */ + public SimpleIdentityLinkAssertionParser(Element xmlAssertion) throws EAAFParserException { + assertionElem = xmlAssertion; + } + + /** + * Constructor for IdentityLinkAssertionParser. + * A DOM-representation of the incoming Inputstream will be created + * @param xmlAssertion <saml:Assertion> as InputStream + * @throws EAAFParserException on any parsing error + */ + public SimpleIdentityLinkAssertionParser(InputStream xmlAssertion) throws EAAFParserException { + try { + assertionElem = DOMUtils.parseXmlValidating(xmlAssertion); + + } + catch (Throwable t) { + throw new EAAFParserException("parser.01", new Object[] { t.toString() }, t.getMessage(), t); + + } + } + + /** + * Parses the identity link from the <saml:Assertion> + * @return Identity link + * @throws EAAFParserException on any parsing error + */ + + public IIdentityLink parseIdentityLink() throws EAAFParserException { + IIdentityLink identityLink; + try { + identityLink = new IdentityLink(); + identityLink.setSamlAssertion(assertionElem); + identityLink.setIssueInstant(assertionElem.getAttribute(ISSUE_INSTANT_ATTR)); + identityLink.setPrPerson((Element) + XPathUtils.selectSingleNode(assertionElem, PERSON_XPATH)); + identityLink.setIdentificationValue( + XPathUtils.getElementValue(assertionElem, PERSON_IDENT_VALUE_XPATH, "")); + identityLink.setIdentificationType( + XPathUtils.getElementValue(assertionElem, PERSON_IDENT_TYPE_XPATH, "")); + + String givenname = XPathUtils.getElementValue(assertionElem, PERSON_GIVEN_NAME_XPATH, ""); + String familyname = XPathUtils.getElementValue(assertionElem, PERSON_FAMILY_NAME_XPATH, ""); + + // replace ' in name with ' + givenname = givenname.replaceAll("'", "'"); + familyname = familyname.replaceAll("'", "'"); + + identityLink.setGivenName(givenname); + identityLink.setFamilyName(familyname); + identityLink.setDateOfBirth( + XPathUtils.getElementValue(assertionElem, PERSON_DATE_OF_BIRTH_XPATH, "")); + NodeIterator dsigRefTransforms = + XPathUtils.selectNodeIterator(assertionElem, DSIG_REFERENCE_TRANSFORMATION_XPATH); + List transElems = new ArrayList(); + Element transformsElem; + while ((transformsElem = (Element) dsigRefTransforms.nextNode()) != null) { + transElems.add(transformsElem); + } + Element[] result = new Element[transElems.size()]; + transElems.toArray(result); + identityLink.setDsigReferenceTransforms(result); + + //identityLink.setPublicKey(getPublicKeys()); + + } + catch (Throwable t) { + throw new EAAFParserException("parser.01", new Object[] { t.toString() }, + t.getMessage(), t); + } + + return identityLink; + } + + /** + * Parses a string array of decoded base64 certificates from + * the <InfoboxReadResponse> found in the dsig-signature + * @return String[] with raw-certificates from the dsig-signature keyinfo + * @throws Exception + */ + public String[] getCertificates() throws Exception { + List certs = new ArrayList(); + NodeIterator rsaIter = + XPathUtils.selectNodeIterator(assertionElem, DSIG_CERTIFICATES_XPATH); + Element certElem; + while ((certElem = (Element) rsaIter.nextNode()) != null) { + String content = DOMUtils.getText(certElem); + certs.add(new String(Base64Utils.decodeFromString(content))); + + } + String[] result = new String[certs.size()]; + certs.toArray(result); + return result; + + } +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/AbstractAuthServletTask.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/AbstractAuthServletTask.java new file mode 100644 index 00000000..a421ff67 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/AbstractAuthServletTask.java @@ -0,0 +1,220 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.core.impl.idp.auth.modules; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileItemFactory; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.lang3.ArrayUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.IRequestStorage; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.api.logging.IRevisionLogger; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.controller.AbstractAuthProtocolModulController; +import at.gv.egiz.eaaf.core.impl.idp.process.springweb.AbstractTask; +import at.gv.egiz.eaaf.core.impl.utils.DataURLBuilder; + +/** + * Task based counterpart to {@link AuthServlet}, providing the same utility methods (error handling, parameter parsing + * etc.).

The code has been taken from {@link AuthServlet}. + */ +public abstract class AbstractAuthServletTask extends AbstractTask { + private static final Logger log = LoggerFactory.getLogger(AbstractAuthServletTask.class); + + @Autowired(required=true) protected IRequestStorage requestStoreage; + @Autowired(required=true) protected IConfiguration authConfig; + + @Autowired protected IRevisionLogger revisionsLogger; + + protected static final String ERROR_CODE_PARAM = "errorid"; + + protected IRequest pendingReq = null; + + public abstract void execute(ExecutionContext executionContext, HttpServletRequest request, + HttpServletResponse response) throws TaskExecutionException; + + + protected final IRequest internalExecute(IRequest pendingReq, ExecutionContext executionContext, HttpServletRequest request, + HttpServletResponse response) throws TaskExecutionException { + //set pending-request object + this.pendingReq = pendingReq; + + //execute task specific action + execute(executionContext, request, response); + + //return pending-request object + return this.pendingReq; + } + + /** + * Redirect the authentication process to protocol specific finalization endpoint. + * + * @param pendingReq Actually processed protocol specific authentication request + * @param httpResp + */ + protected void performRedirectToProtocolFinialization(IRequest pendingReq, HttpServletResponse httpResp) { + performRedirectToItself(pendingReq, httpResp, AbstractAuthProtocolModulController.ENDPOINT_FINALIZEPROTOCOL); + + } + + /** + * Redirect the authentication process to IDP itself + * + * @param pendingReq Actually processed protocol specific authentication request + * @param httpResp + * @param idpEndPoint Servlet EndPoint that should receive the redirect + */ + protected void performRedirectToItself(IRequest pendingReq, HttpServletResponse httpResp, String idpEndPoint) { + String redirectURL = new DataURLBuilder().buildDataURL(pendingReq.getAuthURL(), + idpEndPoint, pendingReq.getPendingRequestId()); + + httpResp.setContentType("text/html"); + httpResp.setStatus(302); + httpResp.addHeader("Location", redirectURL); + log.debug("REDIRECT TO: " + redirectURL); + + } + + + /** + * Parses the request input stream for parameters, assuming parameters are + * encoded UTF-8 (no standard exists how browsers should encode them). + * + * @param req + * servlet request + * + * @return mapping parameter name -> value + * + * @throws IOException + * if parsing request parameters fails. + * + * @throws FileUploadException + * if parsing request parameters fails. + */ + protected Map getParameters(HttpServletRequest req) throws IOException, + FileUploadException { + + Map parameters = new HashMap(); + + if (ServletFileUpload.isMultipartContent(req)) { + // request is encoded as mulitpart/form-data + FileItemFactory factory = new DiskFileItemFactory(); + ServletFileUpload upload = null; + upload = new ServletFileUpload(factory); + List items = null; + items = upload.parseRequest(req); + for (int i = 0; i < items.size(); i++) { + FileItem item = (FileItem) items.get(i); + if (item.isFormField()) { + // Process only form fields - no file upload items + parameters.put(item.getFieldName(), item.getString("UTF-8")); + + //log requests on trace + if (log.isTraceEnabled()) { + String logString = item.getString("UTF-8"); + + // TODO use RegExp + String startS = ""; + String endS = "urn:publicid:gv.at:baseid"; + String logWithMaskedBaseid = logString; + int start = logString.indexOf(startS); + if (start > -1) { + int end = logString.indexOf(endS); + if (end > -1) { + logWithMaskedBaseid = logString.substring(0, start); + logWithMaskedBaseid += startS; + logWithMaskedBaseid += "xxxxxxxxxxxxxxxxxxxxxxxx"; + logWithMaskedBaseid += logString.substring(end, + logString.length()); + } + } + + log.debug("Processed multipart/form-data request parameter: \nName: " + + item.getFieldName() + + "\nValue: " + + logWithMaskedBaseid); + } + + } + } + } + + else { + Iterator> requestParamIt = req.getParameterMap().entrySet().iterator(); + while (requestParamIt.hasNext()) { + Entry entry = requestParamIt.next(); + String key = entry.getKey(); + String[] values = entry.getValue(); + // take the last value from the value array since the legacy code above also does it this way + parameters.put(key, ArrayUtils.isEmpty(values) ? null : values[values.length-1]); + } + + } + + return parameters; + } + + /** + * Reads bytes up to a delimiter, consuming the delimiter. + * + * @param in + * input stream + * @param delimiter + * delimiter character + * @return String constructed from the read bytes + * @throws IOException + */ + protected String readBytesUpTo(InputStream in, char delimiter) + throws IOException { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + boolean done = false; + int b; + while (!done && (b = in.read()) >= 0) { + if (b == delimiter) + done = true; + else + bout.write(b); + } + return bout.toString(); + } + + /** + * Adds a parameter to a URL. + * + * @param url + * the URL + * @param paramname + * parameter name + * @param paramvalue + * parameter value + * @return the URL with parameter added + */ + protected static String addURLParameter(String url, String paramname, + String paramvalue) { + String param = paramname + "=" + paramvalue; + if (url.indexOf("?") < 0) + return url + "?" + param; + else + return url + "&" + param; + } +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/ModuleRegistration.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/ModuleRegistration.java new file mode 100644 index 00000000..cb4a055a --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/ModuleRegistration.java @@ -0,0 +1,151 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.core.impl.idp.auth.modules; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; + +import at.gv.egiz.eaaf.core.api.idp.auth.modules.AuthModule; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.api.idp.process.ProcessEngine; +import at.gv.egiz.eaaf.core.impl.idp.process.ProcessDefinitionParserException; + +/** + * This class handles registering modules. The modules are detected either with + * the ServiceLoader mechanism or via Spring. All detected modules are ranked + * according to their priority. + */ +public class ModuleRegistration { + + private static ModuleRegistration instance = new ModuleRegistration(); + + private List priorizedModules = new ArrayList<>(); + + @Autowired + private ApplicationContext ctx; + + @Autowired + private ProcessEngine processEngine; + + private Logger log = LoggerFactory.getLogger(getClass()); + + public static ModuleRegistration getInstance() { + return instance; + } + + private ModuleRegistration() { + } + + @PostConstruct + private void init() { + // load modules via the ServiceLoader + initServiceLoaderModules(); + + // load modules via Spring + initSpringModules(); + + // order modules according to their priority + sortModules(); + } + + /** + * Discovers modules which use the ServiceLoader mechanism. + */ + private void initServiceLoaderModules() { + log.info("Looking for auth modules."); + ServiceLoader loader = ServiceLoader.load(AuthModule.class); + Iterator modules = loader.iterator(); + while (modules.hasNext()) { + AuthModule module = modules.next(); + log.info("Detected module {}", module.getClass().getName()); + registerModuleProcessDefinitions(module); + priorizedModules.add(module); + } + } + + /** + * Discovers modules which use Spring. + */ + private void initSpringModules() { + log.debug("Discovering Spring modules."); + Map modules = ctx.getBeansOfType(AuthModule.class); + for (AuthModule module : modules.values()) { + registerModuleProcessDefinitions(module); + priorizedModules.add(module); + } + } + + /** + * Registers the resource uris for the module. + * + * @param module + * the module. + */ + private void registerModuleProcessDefinitions(AuthModule module) { + for (String uri : module.getProcessDefinitions()) { + Resource resource = ctx.getResource(uri); + if (resource.isReadable()) { + log.info("Registering process definition '{}'.", uri); + try (InputStream processDefinitionInputStream = resource.getInputStream()) { + processEngine.registerProcessDefinition(processDefinitionInputStream); + } catch (IOException e) { + log.error("Process definition '{}' could NOT be read.", uri, e); + } catch (ProcessDefinitionParserException e) { + log.error("Error while parsing process definition '{}'", uri, e); + } + } else { + log.error("Process definition '{}' cannot be read.", uri); + } + } + } + + /** + * Order the modules in descending order according to their priority. + */ + private void sortModules() { + Collections.sort(priorizedModules, new Comparator() { + @Override + public int compare(AuthModule thisAuthModule, AuthModule otherAuthModule) { + int thisOrder = thisAuthModule.getPriority(); + int otherOrder = otherAuthModule.getPriority(); + return (thisOrder < otherOrder ? 1 : (thisOrder == otherOrder ? 0 : -1)); + } + }); + } + + /** + * Returns the process description id of the first process, in the highest ranked + * module, which is able to work with the given execution context. + * + * @param context + * the {@link ExecutionContext}. + * @return the process id or {@code null} + */ + public String selectProcess(ExecutionContext context) { + for (AuthModule module : priorizedModules) { + String id = module.selectProcess(context); + if (StringUtils.isNotEmpty(id)) { + log.debug("Process with id '{}' selected, for context '{}'.", id, context); + return id; + } + } + log.info("No process is able to handle context '{}'.", context); + return null; + } +} -- cgit v1.2.3