/* * Copyright 2018 A-SIT Plus GmbH * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. * * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "License"); * You may not use this work except in compliance with the License. * You may obtain a copy of the License at: * https://joinup.ec.europa.eu/news/understanding-eupl-v12 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * 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.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.SAXException; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import at.asitplus.eidas.specific.connector.MsConnectorEventCodes; import at.asitplus.eidas.specific.connector.MsEidasNodeConstants; import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ErnbEidData; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.SzrCommunicationException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.AuthBlockSigningService; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.ICcSpecificEidProcessingService; import at.asitplus.eidas.specific.modules.auth.eidas.v2.szr.SzrClient; import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.EidasResponseUtils; import at.gv.e_government.reference.namespace.persondata._20020228.AlternativeNameType; import at.gv.e_government.reference.namespace.persondata._20020228.PersonNameType; import at.gv.e_government.reference.namespace.persondata._20020228.PhysicalPersonType; 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.IConfiguration; import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink; import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.data.Pair; import at.gv.egiz.eaaf.core.impl.idp.auth.builder.BpkBuilder; import at.gv.egiz.eaaf.core.impl.idp.auth.data.AuthProcessDataWrapper; import at.gv.egiz.eaaf.core.impl.idp.auth.data.SimpleIdentityLinkAssertionParser; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; import at.gv.egiz.eaaf.core.impl.utils.DomUtils; import at.gv.egiz.eaaf.core.impl.utils.XPathUtils; import eu.eidas.auth.commons.attribute.AttributeDefinition; import eu.eidas.auth.commons.attribute.AttributeValue; import eu.eidas.auth.commons.light.ILightResponse; import eu.eidas.auth.commons.protocol.eidas.impl.PostalAddress; import lombok.Data; import lombok.extern.slf4j.Slf4j; import szrservices.IdentityLinkType; import szrservices.PersonInfoType; import szrservices.TravelDocumentType; /** * Task that creates the IdentityLink for an eIDAS authenticated person. * * @author tlenz */ @Slf4j @Component("CreateIdentityLinkTask") public class CreateIdentityLinkTask extends AbstractAuthServletTask { @Autowired private IConfiguration basicConfig; @Autowired private SzrClient szrClient; @Autowired private ICcSpecificEidProcessingService eidPostProcessor; @Autowired private AuthBlockSigningService authBlockSigner; private static final String EID_STATUS = "urn:eidgvat:eid.status.eidas"; /* * (non-Javadoc) * * @see at.gv.egovernment.moa.id.process.springweb.MoaIdTask#execute(at.gv. * egovernment.moa.id.process.api.ExecutionContext, * javax.servlet.http.HttpServletRequest, * javax.servlet.http.HttpServletResponse) */ @Override public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) throws TaskExecutionException { try { final AuthProcessDataWrapper authProcessData = pendingReq.getSessionData(AuthProcessDataWrapper.class); final ILightResponse eidasResponse = authProcessData .getGenericDataFromSession(Constants.DATA_FULL_EIDAS_RESPONSE, ILightResponse.class); final Map simpleAttrMap = convertEidasAttrToSimpleMap( eidasResponse.getAttributes().getAttributeMap()); // post-process eIDAS attributes final ErnbEidData eidData = eidPostProcessor.postProcess(simpleAttrMap); // write MDS into technical log and revision log writeMdsLogInformation(eidData); //build IdentityLink or VSZ and eidasBind if (basicConfig.getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_USEDUMMY, false)) { SzrResultHolder idlResult = createDummyIdentityLinkForTestDeployment(eidData); //inject personal-data into session authProcessData.setIdentityLink(idlResult.getIdentityLink()); // set bPK and bPKType into auth session authProcessData.setGenericDataToSession(PvpAttributeDefinitions.BPK_NAME, extendBpkByPrefix( idlResult.getBpK(), pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier())); authProcessData.setGenericDataToSession(PvpAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME, pendingReq.getServiceProviderConfiguration() .getAreaSpecificTargetIdentifier()); } else { //build SZR request from eIDAS data final PersonInfoType personInfo = generateSzrRequest(eidData); //request SZR based on IDL or E-ID mode if (pendingReq.getServiceProviderConfiguration() .isConfigurationValue(MsEidasNodeConstants.PROP_CONFIG_SP_NEW_EID_MODE, false)) { // get encrypted baseId String vsz = szrClient.getEncryptedStammzahl(personInfo); // get eIDAS bind String signedEidasBind = szrClient.getBcBind(vsz, authBlockSigner.getBase64EncodedPublicKey(), EID_STATUS); //get signed AuthBlock String jwsSignature = authBlockSigner.buildSignedAuthBlock(pendingReq); //inject personal-data into session authProcessData.setGenericDataToSession(Constants.SZR_AUTHBLOCK, jwsSignature); authProcessData.setGenericDataToSession(Constants.EIDAS_BIND, signedEidasBind); authProcessData.setEidProcess(true); } else { //request SZR SzrResultHolder idlResult = requestSzrForIdentityLink(personInfo); // write ERnB input-data into revision-log if (basicConfig.getBasicConfigurationBoolean( Constants.CONIG_PROPS_EIDAS_SZRCLIENT_WORKAROUND_REVISIONLOGDATASTORE_ACTIVE, false)) { revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_ERNB_EIDAS_RAW_ID, (String) simpleAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER)); revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_ERNB_EIDAS_ERNB_ID, eidData.getPseudonym()); } //check result-data and write revision-log based on current state checkStateAndWriteRevisionLog(idlResult); //inject personal-data into session authProcessData.setIdentityLink(idlResult.getIdentityLink()); authProcessData.setEidProcess(false); // set bPK and bPKType into auth session authProcessData.setGenericDataToSession(PvpAttributeDefinitions.BPK_NAME, extendBpkByPrefix( idlResult.getBpK(), pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier())); authProcessData.setGenericDataToSession(PvpAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME, pendingReq.getServiceProviderConfiguration() .getAreaSpecificTargetIdentifier()); } } //add generic info's into session authProcessData.setForeigner(true); authProcessData.setGenericDataToSession(PvpAttributeDefinitions.EID_ISSUING_NATION_NAME, EidasResponseUtils .parseEidasPersonalIdentifier((String) simpleAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER)) .getFirst()); authProcessData.setQaaLevel(eidasResponse.getLevelOfAssurance()); // store pending-request requestStoreage.storePendingRequest(pendingReq); } catch (final EidasAttributeException e) { throw new TaskExecutionException(pendingReq, "Minimum required eIDAS attributeset not found.", e); } catch (final EaafException e) { throw new TaskExecutionException(pendingReq, "IdentityLink generation for foreign person FAILED.", e); } catch (final Exception e) { log.error("IdentityLink generation for foreign person FAILED.", e); throw new TaskExecutionException(pendingReq, "IdentityLink generation for foreign person FAILED.", e); } } private PersonInfoType generateSzrRequest(ErnbEidData eidData) { log.debug("Starting connecting SZR Gateway"); final PersonInfoType personInfo = new PersonInfoType(); final PersonNameType personName = new PersonNameType(); final PhysicalPersonType naturalPerson = new PhysicalPersonType(); final TravelDocumentType eDocument = new TravelDocumentType(); naturalPerson.setName(personName); personInfo.setPerson(naturalPerson); personInfo.setTravelDocument(eDocument); // person information personName.setFamilyName(eidData.getFamilyName()); personName.setGivenName(eidData.getGivenName()); naturalPerson.setDateOfBirth(eidData.getFormatedDateOfBirth()); eDocument.setIssuingCountry(eidData.getCitizenCountryCode()); eDocument.setDocumentNumber(eidData.getPseudonym()); // eID document information eDocument.setDocumentType(basicConfig .getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_EDOCUMENTTYPE, Constants.SZR_CONSTANTS_DEFAULT_DOCUMENT_TYPE)); // set PlaceOfBirth if available if (eidData.getPlaceOfBirth() != null) { log.trace("Find 'PlaceOfBirth' attribute: " + eidData.getPlaceOfBirth()); if (basicConfig .getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_SETPLACEOFBIRTHIFAVAILABLE, true)) { naturalPerson.setPlaceOfBirth(eidData.getPlaceOfBirth()); log.trace("Adding 'PlaceOfBirth' to ERnB request ... "); } } // set BirthName if available if (eidData.getBirthName() != null) { log.trace("Find 'BirthName' attribute: " + eidData.getBirthName()); if (basicConfig .getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_SETBIRTHNAMEIFAVAILABLE, true)) { final AlternativeNameType alternativeName = new AlternativeNameType(); naturalPerson.setAlternativeName(alternativeName); alternativeName.setFamilyName(eidData.getBirthName()); log.trace("Adding 'BirthName' to ERnB request ... "); } } return personInfo; } private SzrResultHolder requestSzrForIdentityLink(PersonInfoType personInfo) throws SzrCommunicationException, EaafException { //request IdentityLink from SZR final IdentityLinkType result = szrClient.getIdentityLinkInRawMode(personInfo); final Element idlFromSzr = (Element) result.getAssertion(); IIdentityLink identityLink = new SimpleIdentityLinkAssertionParser(idlFromSzr).parseIdentityLink(); // get bPK from SZR String bpk; if (basicConfig .getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_USESRZFORBPKGENERATION, true)) { bpk = szrClient .getBpk(personInfo, pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier(), basicConfig .getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_VKZ, "no VKZ defined")) .get(0); } else { log.debug("Calculating bPK from baseId ... "); new BpkBuilder(); final Pair bpkCalc = BpkBuilder .generateAreaSpecificPersonIdentifier(identityLink.getIdentificationValue(), identityLink.getIdentificationType(), pendingReq.getServiceProviderConfiguration() .getAreaSpecificTargetIdentifier()); bpk = bpkCalc.getFirst(); } return new SzrResultHolder(identityLink, bpk); } private void checkStateAndWriteRevisionLog(SzrResultHolder idlResult) throws SzrCommunicationException { // write some infos into revision log if (idlResult.getIdentityLink() == null) { log.error("ERnB did not return an identity link."); throw new SzrCommunicationException("ernb.00", null); } revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_IDL_RECEIVED, idlResult.getIdentityLink().getSamlAssertion() .getAttribute(SimpleIdentityLinkAssertionParser.ASSERTIONID)); if (idlResult.getBpK() == null) { log.error("ERnB did not return a bPK for target: " + pendingReq.getServiceProviderConfiguration() .getAreaSpecificTargetIdentifier()); throw new SzrCommunicationException("ernb.01", null); } revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_BPK_RECEIVED); log.debug("ERnB communication was successfull"); } private String extendBpkByPrefix(String bpk, String type) { String bpkType = null; if (type.startsWith(EaafConstants.URN_PREFIX_WBPK)) { bpkType = type.substring(EaafConstants.URN_PREFIX_WBPK.length()); } else if (type.startsWith(EaafConstants.URN_PREFIX_CDID)) { bpkType = type.substring(EaafConstants.URN_PREFIX_CDID.length()); } else if (type.startsWith(EaafConstants.URN_PREFIX_EIDAS)) { bpkType = type.substring(EaafConstants.URN_PREFIX_EIDAS.length()); } if (bpkType != null) { log.trace("Authenticate user with bPK/wbPK " + bpk + " and Type=" + bpkType); return bpkType + ":" + bpk; } else { log.warn("Service Provider Target with: " + type + " is NOT supported. Set bPK as it is ..."); return bpk; } } private Map convertEidasAttrToSimpleMap( ImmutableMap, ImmutableSet>> attributeMap) { final Map result = new HashMap<>(); for (final AttributeDefinition el : attributeMap.keySet()) { final Class parameterizedType = el.getParameterizedType(); if (DateTime.class.equals(parameterizedType)) { final DateTime attribute = EidasResponseUtils.translateDateAttribute(el, attributeMap.get(el).asList()); if (attribute != null) { result.put(el.getFriendlyName(), attribute); log.trace("Find attr '" + el.getFriendlyName() + "' with value: " + attribute.toString()); } else { log.info("Ignore empty 'DateTime' attribute"); } } else if (PostalAddress.class.equals(parameterizedType)) { final PostalAddress addressAttribute = EidasResponseUtils .translateAddressAttribute(el, attributeMap.get(el).asList()); if (addressAttribute != null) { result.put(el.getFriendlyName(), addressAttribute); log.trace("Find attr '" + el.getFriendlyName() + "' with value: " + addressAttribute.toString()); } else { log.info("Ignore empty 'PostalAddress' attribute"); } } else { final List natPersonIdObj = EidasResponseUtils .translateStringListAttribute(el, attributeMap.get(el).asList()); final String stringAttr = natPersonIdObj.get(0); if (StringUtils.isNotEmpty(stringAttr)) { result.put(el.getFriendlyName(), stringAttr); log.trace("Find attr '" + el.getFriendlyName() + "' with value: " + stringAttr); } else { log.info("Ignore empty 'String' attribute"); } } } log.debug("Receive #" + result.size() + " attributes with names: " + result.keySet().toString()); return result; } private void writeMdsLogInformation(ErnbEidData eidData) { // log MDS and country code into technical log if (basicConfig .getBasicConfigurationBoolean(MsEidasNodeConstants.PROP_CONFIG_TECHNICALLOG_WRITE_MDS_INTO_TECH_LOG, false)) { log.info("eIDAS Auth. for user: " + eidData.getGivenName() + " " + eidData.getFamilyName() + " " + eidData .getFormatedDateOfBirth() + " " + "from " + eidData.getCitizenCountryCode()); } // log MDS and country code into revision log if (basicConfig .getBasicConfigurationBoolean(MsEidasNodeConstants.PROP_CONFIG_REVISIONLOG_WRITE_MDS_INTO_REVISION_LOG, false)) { revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_MDSDATA, "{" + eidData.getGivenName() + "," + eidData.getFamilyName() + "," + eidData .getFormatedDateOfBirth() + "," + eidData.getCitizenCountryCode() + "}"); } } @Data private static class SzrResultHolder { final IIdentityLink identityLink; final String bpK; } /** * Build a dummy IdentityLink and a dummy bPK based on eIDAS information. * *

* FOR LOCAL TESTING ONLY!!! * * @param eidData Information from eIDAS response * @return IdentityLink and bPK * @throws ParserConfigurationException In case of an IDL processing error * @throws SAXException In case of an IDL processing error * @throws IOException In case of an IDL processing error * @throws EaafException In case of a bPK generation error */ private SzrResultHolder createDummyIdentityLinkForTestDeployment(ErnbEidData eidData) throws ParserConfigurationException, SAXException, IOException, EaafException { log.warn("SZR-Dummy IS ACTIVE! IdentityLink is NOT VALID!!!!"); // create fake IdL // - fetch IdL template from resources final InputStream s = CreateIdentityLinkTask.class .getResourceAsStream("/resources/xmldata/fakeIdL_IdL_template.xml"); final Element idlTemplate = DomUtils.parseXmlValidating(s); IIdentityLink identityLink = new SimpleIdentityLinkAssertionParser(idlTemplate).parseIdentityLink(); // replace data final Element idlassertion = identityLink.getSamlAssertion(); // - set fake baseID; final Node prIdentification = XPathUtils .selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_IDENT_VALUE_XPATH); prIdentification.getFirstChild().setNodeValue(eidData.getPseudonym()); // - set last name final Node prFamilyName = XPathUtils .selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_FAMILY_NAME_XPATH); prFamilyName.getFirstChild().setNodeValue(eidData.getFamilyName()); // - set first name final Node prGivenName = XPathUtils .selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_GIVEN_NAME_XPATH); prGivenName.getFirstChild().setNodeValue(eidData.getGivenName()); // - set date of birth final Node prDateOfBirth = XPathUtils .selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_DATE_OF_BIRTH_XPATH); prDateOfBirth.getFirstChild().setNodeValue(eidData.getFormatedDateOfBirth()); identityLink = new SimpleIdentityLinkAssertionParser(idlassertion).parseIdentityLink(); final Pair bpkCalc = BpkBuilder .generateAreaSpecificPersonIdentifier(identityLink.getIdentificationValue(), identityLink.getIdentificationType(), pendingReq.getServiceProviderConfiguration() .getAreaSpecificTargetIdentifier()); return new SzrResultHolder(identityLink, bpkCalc.getFirst()); } }