diff options
Diffstat (limited to 'modules/authmodule-eIDAS-v2/src/main/java/at')
31 files changed, 4884 insertions, 0 deletions
| diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java new file mode 100644 index 00000000..a554bf57 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java @@ -0,0 +1,192 @@ +/* + * 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; + +import at.gv.egiz.eaaf.core.api.data.EaafConstants; + +public class Constants { + +  public static final String DATA_REQUESTERID = "req_requesterId"; +  public static final String DATA_PROVIDERNAME = "req_providerName"; +  public static final String DATA_REQUESTED_LOA_LIST = "req_requestedLoA"; +  public static final String DATA_REQUESTED_LOA_COMPERISON = "req_requestedLoAComperision"; +  public static final String DATA_FULL_EIDAS_RESPONSE = "resp_fulleIDASResponse"; + +  // templates for post-binding forwarding +  public static final String TEMPLATE_POST_FORWARD_NAME = "eidas_node_forward.html"; +  public static final String TEMPLATE_POST_FORWARD_ENDPOINT = "endPoint"; +  public static final String TEMPLATE_POST_FORWARD_TOKEN_NAME = "tokenName"; +  public static final String TEMPLATE_POST_FORWARD_TOKEN_VALUE = "tokenValue"; + +  // configuration properties +  public static final String CONIG_PROPS_EIDAS_PREFIX = "auth.eIDAS"; +  public static final String CONIG_PROPS_EIDAS_IS_TEST_IDENTITY = CONIG_PROPS_EIDAS_PREFIX  +      + ".eid.testidentity.default"; +  public static final String CONIG_PROPS_EIDAS_NODE = CONIG_PROPS_EIDAS_PREFIX + ".node_v2"; +  public static final String CONIG_PROPS_EIDAS_NODE_COUNTRYCODE = CONIG_PROPS_EIDAS_NODE + ".countrycode"; +  public static final String CONIG_PROPS_EIDAS_NODE_PUBLICSECTOR_TARGETS = CONIG_PROPS_EIDAS_NODE +      + ".publicSectorTargets"; +  public static final String CONIG_PROPS_EIDAS_NODE_ENTITYID = CONIG_PROPS_EIDAS_NODE + ".entityId"; +  public static final String CONIG_PROPS_EIDAS_CONNECTOR_NODE_FORWARD_URL = CONIG_PROPS_EIDAS_NODE +      + ".forward.endpoint"; +     +  public static final String CONIG_PROPS_EIDAS_NODE_FORWARD_METHOD = CONIG_PROPS_EIDAS_NODE +      + ".forward.method"; +  public static final String CONIG_PROPS_EIDAS_NODE_ATTRIBUTES_REQUESTED_DEFAULT_ONLYNATURAL = +      CONIG_PROPS_EIDAS_NODE + ".attributes.requested.onlynatural"; +  public static final String CONIG_PROPS_EIDAS_NODE_ATTRIBUTES_REQUESTED_CC_SPECIFIC_ONLYNATURAL = +      CONIG_PROPS_EIDAS_NODE + ".attributes.requested.{0}.onlynatural"; +  public static final String CONIG_PROPS_EIDAS_NODE_ATTRIBUTES_REQUESTED_REPRESENTATION = +      CONIG_PROPS_EIDAS_NODE + ".attributes.requested.representation"; +   +  public static final String CONIG_PROPS_EIDAS_NODE_REQUESTERID_USE_HASHED_VERSION = +      CONIG_PROPS_EIDAS_NODE + ".requesterId.useHashedForm"; +  public static final String CONIG_PROPS_EIDAS_NODE_WORKAROUND_USE_STATIC_REQUESTERID_FOR_LUX = +      CONIG_PROPS_EIDAS_NODE + ".requesterId.lu.useStaticRequesterForAll"; +   +  public static final String CONIG_PROPS_EIDAS_NODE_WORKAROUND_ADD_ALWAYS_PROVIDERNAME = +      CONIG_PROPS_EIDAS_NODE + ".workarounds.addAlwaysProviderName";   +  public static final String CONIG_PROPS_EIDAS_NODE_WORKAROUND_USEREQUESTIDASTRANSACTIONIDENTIFIER = +      CONIG_PROPS_EIDAS_NODE + ".workarounds.useRequestIdAsTransactionIdentifier"; +   +  public static final String CONFIG_PROP_EIDAS_NODE_NAMEIDFORMAT =  +      CONIG_PROPS_EIDAS_NODE + ".requested.nameIdFormat"; +   +  public static final String CONIG_PROPS_EIDAS_NODE_STATIC_PROVIDERNAME_FOR_PUBLIC_SP = CONIG_PROPS_EIDAS_NODE +      + ".staticProviderNameForPublicSPs"; +  public static final String DEFAULT_PROPS_EIDAS_NODE_STATIC_PROVIDERNAME_FOR_PUBLIC_SP = "Austria"; + +  public static final String FORWARD_METHOD_POST = "POST"; +  public static final String FORWARD_METHOD_GET = "GET"; + +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT = CONIG_PROPS_EIDAS_PREFIX + ".szrclient"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_USETESTSERVICE = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".useTestService"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_TRACEMESSAGES = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".debug.logfullmessages"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_USEDUMMY = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".debug.useDummySolution"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_SET_MDS_TO_EIDASBIND = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".eidasbind.mds.inject"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_TIMEOUT_CONNECTION = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".timeout.connection"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_TIMEOUT_RESPONSE = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".timeout.response"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_ENDPOINT_PROD = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".endpoint.prod"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_ENDPOINT_TEST = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".endpoint.test"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_PATH = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".ssl.keyStore.path"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_PASSWORD = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".ssl.keyStore.password"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_PATH = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".ssl.trustStore.path"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_PASSWORD = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".ssl.trustStore.password"; + +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_EDOCUMENTTYPE = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".params.documenttype"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_VKZ = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".params.vkz"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_ISSUING_DATE = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".params.issuingdate"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_ISSUING_AUTHORITY = +      CONIG_PROPS_EIDAS_SZRCLIENT + ".params.issuingauthority"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_KEYS_USEDUMMY = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".params.usedummykeys"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_USESRZFORBPKGENERATION = +      CONIG_PROPS_EIDAS_SZRCLIENT + ".params.useSZRForbPKCalculation"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_SETPLACEOFBIRTHIFAVAILABLE = +      CONIG_PROPS_EIDAS_SZRCLIENT + ".params.setPlaceOfBirthIfAvailable"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_SETBIRTHNAMEIFAVAILABLE = +      CONIG_PROPS_EIDAS_SZRCLIENT + ".params.setBirthNameIfAvailable"; + +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_WORKAROUND_REVISIONLOGDATASTORE_ACTIVE = +      CONIG_PROPS_EIDAS_SZRCLIENT + ".revisionlog.eidmapping.active"; + +  public static final String DEFAULT_MS_NODE_COUNTRY_CODE = "AT"; +   +  @Deprecated +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_WORKAROUND_SQLLITEDATASTORE_URL = +      CONIG_PROPS_EIDAS_SZRCLIENT + ".workarounds.datastore.sqlite.url"; +  @Deprecated +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_WORKAROUND_SQLLITEDATASTORE_ACTIVE = +      CONIG_PROPS_EIDAS_SZRCLIENT + ".workarounds.datastore.sqlite.active"; + +  // http endpoint descriptions +  public static final String eIDAS_HTTP_ENDPOINT_SP_POST = "/eidas/light/sp/post"; +  public static final String eIDAS_HTTP_ENDPOINT_SP_REDIRECT = "/eidas/light/sp/redirect"; +  public static final String eIDAS_HTTP_ENDPOINT_IDP_COLLEAGUEREQUEST = "/eidas/light/ColleagueRequest"; +  public static final String eIDAS_HTTP_ENDPOINT_METADATA = "/eidas/light/metadata"; + +  // eIDAS request parameters +  public static final String eIDAS_REQ_NAMEID_FORMAT = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"; + +  // eIDAS attribute names +  public static final String eIDAS_ATTR_PERSONALIDENTIFIER = "PersonIdentifier"; +  public static final String eIDAS_ATTR_DATEOFBIRTH = "DateOfBirth"; +  public static final String eIDAS_ATTR_CURRENTGIVENNAME = "FirstName"; +  public static final String eIDAS_ATTR_CURRENTFAMILYNAME = "FamilyName"; +  public static final String eIDAS_ATTR_PLACEOFBIRTH = "PlaceOfBirth"; +  public static final String eIDAS_ATTR_BIRTHNAME = "BirthName"; +  public static final String eIDAS_ATTR_CURRENTADDRESS = "CurrentAddress"; + +  public static final String eIDAS_ATTR_LEGALPERSONIDENTIFIER = "LegalPersonIdentifier"; +  public static final String eIDAS_ATTR_LEGALNAME = "LegalName"; + +  public static final String eIDAS_ATTR_REPRESENTATIVE_PERSONALIDENTIFIER = "RepresentativePersonIdentifier"; +  public static final String eIDAS_ATTR_REPRESENTATIVE_DATEOFBIRTH = "RepresentativeDateOfBirth"; +  public static final String eIDAS_ATTR_REPRESENTATIVE_CURRENTGIVENNAME = "RepresentativeFirstName"; +  public static final String eIDAS_ATTR_REPRESENTATIVE_CURRENTFAMILYNAME = "RepresentativeFamilyName"; +   +   +  public static final String eIDAS_REQ_PARAM_SECTOR_PUBLIC = "public"; +  public static final String eIDAS_REQ_PARAM_SECTOR_PRIVATE = "private"; +   +  public static final String POLICY_DEFAULT_ALLOWED_TARGETS = +      EaafConstants.URN_PREFIX_CDID.replaceAll("\\.", "\\\\.").replaceAll("\\+", "\\\\+") + ".*"; + +  // SAML2 Constants +  public static final String SUCCESS_URI = "urn:oasis:names:tc:SAML:2.0:status:Success"; +  public static final String ERROR_URI = "urn:oasis:names:tc:SAML:2.0:status:Responder"; + +  public static final String HTTP_CLIENT_DEFAULT_TIMEOUT_CONNECTION = "30"; // seconds +  public static final String HTTP_CLIENT_DEFAULT_TIMEOUT_RESPONSE = "60"; // seconds + +  public static final String SZR_SCHEMA_LOCATIONS = +      "urn:SZRServices" + " " + "/szr_client/szr.xsd"; + +  // Default values for SZR communication +  public static final String SZR_CONSTANTS_DEFAULT_DOCUMENT_TYPE = "ELEKTR_DOKUMENT"; + +  // TODO remove!!! +  public static final String SZR_CONSTANTS_DEFAULT_ISSUING_DATE = "2014-01-01"; +  public static final String SZR_CONSTANTS_DEFAULT_ISSUING_AUTHORITY = "ms-specific eIDAS-Node for AT"; +  public static final String SZR_CONSTANTS_DEFAULT_PUBKEY_EXPONENT = "AQAB"; +  public static final String SZR_CONSTANTS_DEFAULT_PUBKEY_MODULUS = +      "AJZyj/+sdCMDRq9RkvbFcgSTVn/OfS8EUE81ddwP8MNuJ1kd1SWBUJPaQX2JLJHrL54mkOhrkhH2M/zcuOTu8nW9TOEg" +      + "XGjrRB/0HpiYKpV+VDJViyyc/GacNLxN4Anw4pima6gHYaJIw9hQkL/nuO2hyh8PGJd7rxeFXJmbLy+X"; + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/EidasAuthenticationModulImpl.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/EidasAuthenticationModulImpl.java new file mode 100644 index 00000000..85f0873e --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/EidasAuthenticationModulImpl.java @@ -0,0 +1,87 @@ +/* + * 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; + +import java.io.Serializable; + +import org.apache.commons.lang3.StringUtils; + +import at.asitplus.eidas.specific.core.MsEidasNodeConstants; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.idp.auth.modules.AuthModule; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; + +/** + * eIDAS authentication-process selector. + *  + * @author tlenz + * + */ +public class EidasAuthenticationModulImpl implements AuthModule { + +  private int priority = 1; + +  @Override +  public int getPriority() { +    return priority; +  } + +  /** +   * Sets the priority of this module. Default value is {@code 0}. +   *  +   * @param priority The priority. +   */ +  public void setPriority(int priority) { +    this.priority = priority; +  } + +  /* +   * (non-Javadoc) +   *  +   * @see at.gv.egovernment.moa.id.auth.modules.AuthModule#selectProcess(at.gv. +   * egovernment.moa.id.process.api.ExecutionContext) +   */ +  @Override +  public String selectProcess(ExecutionContext context, IRequest pendingReq) { +    Serializable flagObj = context.get(MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY); +    if (flagObj != null && flagObj instanceof String  +        && StringUtils.isNotBlank((String) context.get(MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY))) { +      return "eIDASAuthentication_v2"; +    } else { +      return null; +    } + +  } + +  /* +   * (non-Javadoc) +   *  +   * @see at.gv.egovernment.moa.id.auth.modules.AuthModule#getProcessDefinitions() +   */ +  @Override +  public String[] getProcessDefinitions() { +    return new String[] { "classpath:eIDAS.Authentication.process.xml" }; +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/EidasAuthenticationSpringResourceProvider.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/EidasAuthenticationSpringResourceProvider.java new file mode 100644 index 00000000..535e4f97 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/EidasAuthenticationSpringResourceProvider.java @@ -0,0 +1,52 @@ +/* + * 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; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import at.gv.egiz.components.spring.api.SpringResourceProvider; + +public class EidasAuthenticationSpringResourceProvider implements SpringResourceProvider { + +  @Override +  public String getName() { +    return "Auth. module for eIDAS Ref. Impl. v2.x"; +  } + +  @Override +  public String[] getPackagesToScan() { +    // TODO Auto-generated method stub +    return null; +  } + +  @Override +  public Resource[] getResourcesToLoad() { +    final ClassPathResource eidasAuthConfig = new ClassPathResource("/eidas_v2_auth.beans.xml", +        EidasAuthenticationSpringResourceProvider.class); + +    return new Resource[] { eidasAuthConfig }; +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/EidasSignalServlet.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/EidasSignalServlet.java new file mode 100644 index 00000000..d3cac80c --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/EidasSignalServlet.java @@ -0,0 +1,161 @@ +/* + * 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; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +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.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import com.google.common.collect.ImmutableSortedSet; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasSAuthenticationException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.EidasAttributeRegistry; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.idp.controller.AbstractProcessEngineSignalController; +import eu.eidas.auth.commons.EidasParameterKeys; +import eu.eidas.auth.commons.light.ILightResponse; +import eu.eidas.specificcommunication.SpecificCommunicationDefinitionBeanNames; +import eu.eidas.specificcommunication.exception.SpecificCommunicationException; +import eu.eidas.specificcommunication.protocol.SpecificCommunicationService; + +/** + * Controler implementation for eIDAS Node communication. + *  + * @author tlenz + * + */ +@Controller +public class EidasSignalServlet extends AbstractProcessEngineSignalController { + +  private static final Logger log = LoggerFactory.getLogger(EidasSignalServlet.class); +  @Autowired +  private ApplicationContext context; +  @Autowired +  private EidasAttributeRegistry attrRegistry; + +  /** +   * eIDAS Node communication end-point implementation. +   *  +   */ +  public EidasSignalServlet() { +    super(); +    log.debug("Registering servlet {} with mappings '{}' and '{}'.",  +        getClass().getName(), Constants.eIDAS_HTTP_ENDPOINT_SP_POST,  +        Constants.eIDAS_HTTP_ENDPOINT_SP_REDIRECT); + +  } + +  @RequestMapping(value = {  +      Constants.eIDAS_HTTP_ENDPOINT_SP_POST, +      Constants.eIDAS_HTTP_ENDPOINT_SP_REDIRECT +      }, +      method = { RequestMethod.POST, RequestMethod.GET }) +  public void restoreEidasAuthProcess(HttpServletRequest req, HttpServletResponse resp) throws IOException, +      EaafException { +    signalProcessManagement(req, resp); +  } + +  /** +   * Protocol specific implementation to get the pending-requestID from http +   * request object. +   * +   * @param request The http Servlet-Request object +   * @return The Pending-request id +   * +   */ +  @Override +  public String getPendingRequestId(HttpServletRequest request) { +    // String sessionId = super.getPendingRequestId(request); + +    try { +      // get token from Request +      final String tokenBase64 = request.getParameter(EidasParameterKeys.TOKEN.toString()); +      if (StringUtils.isEmpty(tokenBase64)) { +        log.warn("NO eIDAS message token found."); +        throw new EidasSAuthenticationException("eidas.04", null); + +      } +      log.trace("Receive eIDAS-node token: " + tokenBase64 + " Starting transaction-restore process ... "); + +      final SpecificCommunicationService specificConnectorCommunicationService = +          (SpecificCommunicationService) context.getBean( +              SpecificCommunicationDefinitionBeanNames.SPECIFIC_CONNECTOR_COMMUNICATION_SERVICE.toString()); +      final ILightResponse eidasResponse = specificConnectorCommunicationService.getAndRemoveResponse( +          tokenBase64, +          ImmutableSortedSet.copyOf(attrRegistry.getCoreAttributeRegistry().getAttributes())); + +      String pendingReqId = null; +      if (StringUtils.isEmpty(eidasResponse.getRelayState())) { +        log.debug("eIDAS Node returns no RelayState. "); + +        if (authConfig.getBasicConfigurationBoolean( +            Constants.CONIG_PROPS_EIDAS_NODE_WORKAROUND_USEREQUESTIDASTRANSACTIONIDENTIFIER, +            false)) { +          log.trace("Use lightRequestId to recover session ... "); +          pendingReqId = transactionStorage.get(eidasResponse.getInResponseToId(), String.class); +          if (StringUtils.isNotEmpty(pendingReqId)) { +            log.debug("Restoring session with lightRequestId ... "); +            transactionStorage.remove(eidasResponse.getInResponseToId()); + +          } +        } + +      } else { +        log.debug("Find transaction identifier in SAML2 'RelayState': " + eidasResponse.getRelayState()); +        pendingReqId = eidasResponse.getRelayState(); + +      } + +      if (StringUtils.isNotEmpty(pendingReqId)) { +        request.setAttribute(Constants.DATA_FULL_EIDAS_RESPONSE, eidasResponse); +        return pendingReqId; + +      } + +      log.info("NO transaction identifier found! Stopping process ...."); +      log.trace("FullResponse: " + eidasResponse.toString()); + +    } catch (final SpecificCommunicationException e) { +      log.warn("Can NOT load eIDAS Response from cache.", e); +      log.debug("eIDAS response token was: " + request.getParameter(EidasParameterKeys.TOKEN.toString())); + +    } catch (final Exception e) { +      log.warn("Unable to retrieve moa session id.", e); + +    } + +    return null; +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/ErnbEidData.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/ErnbEidData.java new file mode 100644 index 00000000..6c7eeb6b --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/ErnbEidData.java @@ -0,0 +1,115 @@ +/* + * 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.dao; + +import java.text.SimpleDateFormat; + +import org.joda.time.DateTime; + +import at.gv.e_government.reference.namespace.persondata._20020228.PostalAddressType; + +public class ErnbEidData { + +  private String citizenCountryCode = null; + +  // MDS +  private String pseudonym = null; +  private String givenName = null; +  private String familyName = null; +  private DateTime dateOfBirth = null; + +  // additional attributes +  private String placeOfBirth = null; +  private String birthName = null; +  private PostalAddressType address = null; + +  public String getCitizenCountryCode() { +    return citizenCountryCode; +  } + +  public void setCitizenCountryCode(String citizenCountryCode) { +    this.citizenCountryCode = citizenCountryCode; +  } + +  public String getPseudonym() { +    return pseudonym; +  } + +  public void setPseudonym(String pseudonym) { +    this.pseudonym = pseudonym; +  } + +  public String getGivenName() { +    return givenName; +  } + +  public void setGivenName(String givenName) { +    this.givenName = givenName; +  } + +  public String getFamilyName() { +    return familyName; +  } + +  public void setFamilyName(String familyName) { +    this.familyName = familyName; +  } + +  public DateTime getDateOfBirth() { +    return dateOfBirth; +  } + +  public void setDateOfBirth(DateTime dateOfBirth) { +    this.dateOfBirth = dateOfBirth; +  } + +  public String getPlaceOfBirth() { +    return placeOfBirth; +  } + +  public void setPlaceOfBirth(String placeOfBirth) { +    this.placeOfBirth = placeOfBirth; +  } + +  public String getBirthName() { +    return birthName; +  } + +  public void setBirthName(String birthName) { +    this.birthName = birthName; +  } + +  public PostalAddressType getAddress() { +    return address; +  } + +  public void setAddress(PostalAddressType address) { +    this.address = address; +  } + +  public String getFormatedDateOfBirth() { +    return new SimpleDateFormat("yyyy-MM-dd").format(dateOfBirth.toDate()); +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/EidPostProcessingException.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/EidPostProcessingException.java new file mode 100644 index 00000000..f4c0be67 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/EidPostProcessingException.java @@ -0,0 +1,40 @@ +/* + * 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.exception; + +public class EidPostProcessingException extends EidasSAuthenticationException { + +  private static final long serialVersionUID = 6780652273831172456L; + +  public EidPostProcessingException(String internalMsgId, Object[] params) { +    super(internalMsgId, params); + +  } + +  public EidPostProcessingException(String internalMsgId, Object[] params, Throwable e) { +    super(internalMsgId, params, e); + +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/EidasAttributeException.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/EidasAttributeException.java new file mode 100644 index 00000000..49736d58 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/EidasAttributeException.java @@ -0,0 +1,34 @@ +/* + * 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.exception; + +public class EidasAttributeException extends EidasSAuthenticationException { +  private static final long serialVersionUID = 1L; + +  public EidasAttributeException(String attrbuteName) { +    super("eidas.00", new Object[] { attrbuteName }); + +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/EidasSAuthenticationException.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/EidasSAuthenticationException.java new file mode 100644 index 00000000..8ff218e3 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/EidasSAuthenticationException.java @@ -0,0 +1,41 @@ +/* + * 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.exception; + +import at.gv.egiz.eaaf.core.exceptions.EaafAuthenticationException; + +public class EidasSAuthenticationException extends EaafAuthenticationException { + + +  private static final long serialVersionUID = 1L; + +  public EidasSAuthenticationException(String internalMsgId, Object[] params) { +    super(internalMsgId, params); +  } + +  public EidasSAuthenticationException(String internalMsgId, Object[] params, Throwable e) { +    super(internalMsgId, params, e); +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/EidasValidationException.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/EidasValidationException.java new file mode 100644 index 00000000..2988dd6f --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/EidasValidationException.java @@ -0,0 +1,34 @@ +/* + * 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.exception; + +public class EidasValidationException extends EidasSAuthenticationException { + +  private static final long serialVersionUID = 1L; + +  public EidasValidationException(String internalMsgId, Object[] params) { +    super(internalMsgId, params); +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/SqliteServiceException.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/SqliteServiceException.java new file mode 100644 index 00000000..d48abec9 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/SqliteServiceException.java @@ -0,0 +1,40 @@ +/* + * 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.exception; + +public class SqliteServiceException extends EidasSAuthenticationException { + +  private static final long serialVersionUID = 2278259367925102676L; + +  public SqliteServiceException(String internalMsgId, Object[] params, Throwable e) { +    super(internalMsgId, params, e); + +  } + +  public SqliteServiceException(String internalMsgId, Object[] params) { +    super(internalMsgId, params); + +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/SzrCommunicationException.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/SzrCommunicationException.java new file mode 100644 index 00000000..c736cadb --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/SzrCommunicationException.java @@ -0,0 +1,38 @@ +/* + * 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.exception; + +public class SzrCommunicationException extends EidasSAuthenticationException { + +  private static final long serialVersionUID = 1L; + +  public SzrCommunicationException(String internalMsgId, Object[] params) { +    super(internalMsgId, params); +  } + +  public SzrCommunicationException(String internalMsgId, Object[] params, Throwable e) { +    super(internalMsgId, params, e); +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/AbstractEidProcessor.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/AbstractEidProcessor.java new file mode 100644 index 00000000..4a3218e9 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/AbstractEidProcessor.java @@ -0,0 +1,425 @@ +/* + * 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.handler; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Base64; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; + +import com.google.common.collect.ImmutableSortedSet; + +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.EidPostProcessingException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.EidasAttributeRegistry; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.EidasResponseUtils; +import at.gv.e_government.reference.namespace.persondata._20020228.PostalAddressType; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.data.EaafConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfigurationWithSP; +import at.gv.egiz.eaaf.core.api.idp.ISpConfiguration; +import at.gv.egiz.eaaf.core.impl.data.Triple; +import eu.eidas.auth.commons.attribute.AttributeDefinition; +import eu.eidas.auth.commons.attribute.ImmutableAttributeMap; +import eu.eidas.auth.commons.light.impl.LightRequest.Builder; +import eu.eidas.auth.commons.protocol.eidas.SpType; +import eu.eidas.auth.commons.protocol.eidas.impl.PostalAddress; + +public abstract class AbstractEidProcessor implements INationalEidProcessor { +  private static final Logger log = LoggerFactory.getLogger(AbstractEidProcessor.class); + +  @Autowired +  protected EidasAttributeRegistry attrRegistry; +  @Autowired +  protected IConfigurationWithSP basicConfig; + +  @Override +  public final void preProcess(IRequest pendingReq, Builder authnRequestBuilder) { + +    buildLevelOfAssurance(pendingReq.getServiceProviderConfiguration(), authnRequestBuilder); +    buildProviderNameAndRequesterIdAttribute(pendingReq, authnRequestBuilder); +    buildRequestedAttributes(authnRequestBuilder); + +  } + + +  @Override +  public final ErnbEidData postProcess(Map<String, Object> eidasAttrMap) throws EidPostProcessingException, +      EidasAttributeException { +    final ErnbEidData result = new ErnbEidData(); + +    final Object eIdentifierObj = eidasAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); +    final Triple<String, String, String> eIdentifier = +        EidasResponseUtils.parseEidasPersonalIdentifier((String) eIdentifierObj); +    result.setCitizenCountryCode(eIdentifier.getFirst()); + +    // MDS attributes +    result.setPseudonym(processPseudonym(eidasAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER))); +    result.setFamilyName(processFamilyName(eidasAttrMap.get(Constants.eIDAS_ATTR_CURRENTFAMILYNAME))); +    result.setGivenName(processGivenName(eidasAttrMap.get(Constants.eIDAS_ATTR_CURRENTGIVENNAME))); +    result.setDateOfBirth(processDateOfBirth(eidasAttrMap.get(Constants.eIDAS_ATTR_DATEOFBIRTH))); + +    // additional attributes +    result.setPlaceOfBirth(processPlaceOfBirth(eidasAttrMap.get(Constants.eIDAS_ATTR_PLACEOFBIRTH))); +    result.setBirthName(processBirthName(eidasAttrMap.get(Constants.eIDAS_ATTR_BIRTHNAME))); +    result.setAddress(processAddress(eidasAttrMap.get(Constants.eIDAS_ATTR_CURRENTADDRESS))); + +    return result; + +  } + +   +  /** +   * Get a Map of country-specific requested attributes. +   *  +   * @return +   */ +  @NonNull +  protected abstract Map<String, Boolean> getCountrySpecificRequestedAttributes(); + +  /** +   * Post-Process the eIDAS CurrentAddress attribute. +   *  +   * @param currentAddressObj eIDAS current address information +   * @return current address or null if no attribute is available +   * @throws EidPostProcessingException if post-processing fails +   * @throws EidasAttributeException    if eIDAS attribute is of a wrong type +   */ +  protected PostalAddressType processAddress(Object currentAddressObj) throws EidPostProcessingException, +      EidasAttributeException { + +    if (currentAddressObj != null) { +      if (currentAddressObj instanceof PostalAddress) { +        final PostalAddressType result = new PostalAddressType(); +        result.setPostalCode(((PostalAddress) currentAddressObj).getPostCode()); +        result.setMunicipality(((PostalAddress) currentAddressObj).getPostName()); + +        // TODO: add more mappings +         +        return result; + +      } else { +        log.warn("eIDAS attr: " + Constants.eIDAS_ATTR_CURRENTADDRESS + " is of WRONG type"); +        throw new EidasAttributeException(Constants.eIDAS_ATTR_CURRENTADDRESS); + +      } + +    } else { +      log.debug("NO '" + Constants.eIDAS_ATTR_CURRENTADDRESS + "' attribute. Post-Processing skipped ... "); +    } + +    return null; + +  } + +  /** +   * Post-Process the eIDAS BirthName attribute. +   *  +   * @param birthNameObj eIDAS birthname information +   * @return birthName or null if no attribute is available +   * @throws EidPostProcessingException if post-processing fails +   * @throws EidasAttributeException    if eIDAS attribute is of a wrong type +   */ +  protected String processBirthName(Object birthNameObj) throws EidPostProcessingException, +      EidasAttributeException { +    if (birthNameObj != null) { +      if (birthNameObj instanceof String) { +        return (String) birthNameObj; + +      } else { +        log.warn("eIDAS attr: " + Constants.eIDAS_ATTR_BIRTHNAME + " is of WRONG type"); +        throw new EidasAttributeException(Constants.eIDAS_ATTR_BIRTHNAME); + +      } + +    } else { +      log.debug("NO '" + Constants.eIDAS_ATTR_BIRTHNAME + "' attribute. Post-Processing skipped ... "); +    } + +    return null; + +  } + +  /** +   * Post-Process the eIDAS PlaceOfBirth attribute. +   *  +   * @param placeOfBirthObj eIDAS Place-of-Birth information +   * @return place of Birth or null if no attribute is available +   * @throws EidPostProcessingException if post-processing fails +   * @throws EidasAttributeException    if eIDAS attribute is of a wrong type +   */ +  protected String processPlaceOfBirth(Object placeOfBirthObj) throws EidPostProcessingException, +      EidasAttributeException { +    if (placeOfBirthObj != null) { +      if (placeOfBirthObj instanceof String) { +        return (String) placeOfBirthObj; + +      } else { +        log.warn("eIDAS attr: " + Constants.eIDAS_ATTR_PLACEOFBIRTH + " is of WRONG type"); +        throw new EidasAttributeException(Constants.eIDAS_ATTR_PLACEOFBIRTH); + +      } + +    } else { +      log.debug("NO '" + Constants.eIDAS_ATTR_PLACEOFBIRTH + "' attribute. Post-Processing skipped ... "); +    } + +    return null; + +  } + +  /** +   * Post-Process the eIDAS DateOfBirth attribute. +   *  +   * @param dateOfBirthObj eIDAS date-of-birth attribute information +   * @return formated user's date-of-birth +   * @throws EidasAttributeException    if NO attribute is available +   * @throws EidPostProcessingException if post-processing fails +   */ +  protected DateTime processDateOfBirth(Object dateOfBirthObj) throws EidPostProcessingException, +      EidasAttributeException { +    if (dateOfBirthObj == null || !(dateOfBirthObj instanceof DateTime)) { +      throw new EidasAttributeException(Constants.eIDAS_ATTR_DATEOFBIRTH); +    } + +    return (DateTime) dateOfBirthObj; + +  } + +  /** +   * Post-Process the eIDAS GivenName attribute. +   *  +   * @param givenNameObj eIDAS givenName attribute information +   * @return formated user's givenname +   * @throws EidasAttributeException    if NO attribute is available +   * @throws EidPostProcessingException if post-processing fails +   */ +  protected String processGivenName(Object givenNameObj) throws EidPostProcessingException, +      EidasAttributeException { +    if (givenNameObj == null || !(givenNameObj instanceof String)) { +      throw new EidasAttributeException(Constants.eIDAS_ATTR_CURRENTGIVENNAME); +    } + +    return (String) givenNameObj; + +  } + +  /** +   * Post-Process the eIDAS FamilyName attribute. +   *  +   * @param familyNameObj eIDAS familyName attribute information +   * @return formated user's familyname +   * @throws EidasAttributeException    if NO attribute is available +   * @throws EidPostProcessingException if post-processing fails +   */ +  protected String processFamilyName(Object familyNameObj) throws EidPostProcessingException, +      EidasAttributeException { +    if (familyNameObj == null || !(familyNameObj instanceof String)) { +      throw new EidasAttributeException(Constants.eIDAS_ATTR_CURRENTFAMILYNAME); +    } + +    return (String) familyNameObj; + +  } + +  /** +   * Post-Process the eIDAS pseudonym to ERnB unique identifier. +   *  +   * @param personalIdObj eIDAS PersonalIdentifierAttribute +   * @return Unique personal identifier without country-code information +   * @throws EidasAttributeException    if NO attribute is available +   * @throws EidPostProcessingException if post-processing fails +   */ +  protected String processPseudonym(Object personalIdObj) throws EidPostProcessingException, +      EidasAttributeException { +    if (personalIdObj == null || !(personalIdObj instanceof String)) { +      throw new EidasAttributeException(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); +    } + +    final Triple<String, String, String> eIdentifier = +        EidasResponseUtils.parseEidasPersonalIdentifier((String) personalIdObj); + +    return eIdentifier.getThird(); + +  } + +  /** +   * Set ProviderName and RequestId into eIDAS AuthnRequest. +   *  +   * @param pendingReq Current pendingRequest +   * @param authnRequestBuilder AuthnRequest builder +   */ +  protected void buildProviderNameAndRequesterIdAttribute(IRequest pendingReq, Builder authnRequestBuilder) { +    final ISpConfiguration spConfig = pendingReq.getServiceProviderConfiguration(); + +    // set correct SPType for requested target sector +    final String publicSectorTargetSelector = basicConfig.getBasicConfiguration( +        Constants.CONIG_PROPS_EIDAS_NODE_PUBLICSECTOR_TARGETS, +        Constants.POLICY_DEFAULT_ALLOWED_TARGETS); +    final Pattern p = Pattern.compile(publicSectorTargetSelector); +    final Matcher m = p.matcher(spConfig.getAreaSpecificTargetIdentifier()); +    if (m.matches()) { +      log.debug("Map " + spConfig.getAreaSpecificTargetIdentifier() + " to 'PublicSector'"); +      authnRequestBuilder.spType(SpType.PUBLIC.getValue()); + +      final String providerName = pendingReq.getRawData(Constants.DATA_PROVIDERNAME, String.class); +      if (basicConfig.getBasicConfigurationBoolean( +              Constants.CONIG_PROPS_EIDAS_NODE_WORKAROUND_ADD_ALWAYS_PROVIDERNAME, +              false)) { +        //TODO: only for eIDAS ref. node 2.0 and 2.1 because it need 'Providername' for +        if (StringUtils.isNotEmpty(providerName)) { +          log.debug("Set 'providername' to: {}", providerName); +          authnRequestBuilder.providerName(providerName);   +           +        } else { +          authnRequestBuilder.providerName(basicConfig.getBasicConfiguration( +              Constants.CONIG_PROPS_EIDAS_NODE_STATIC_PROVIDERNAME_FOR_PUBLIC_SP, +              Constants.DEFAULT_PROPS_EIDAS_NODE_STATIC_PROVIDERNAME_FOR_PUBLIC_SP)); +           +        }                  +      } + +    } else { +      log.debug("Map " + spConfig.getAreaSpecificTargetIdentifier() + " to 'PrivateSector'"); +      authnRequestBuilder.spType(SpType.PRIVATE.getValue()); + +      // TODO: switch to RequesterId in further version +      // set provider name for private sector applications +      final String providerName = pendingReq.getRawData(Constants.DATA_PROVIDERNAME, String.class); +      if (StringUtils.isNotEmpty(providerName)) { +        authnRequestBuilder.providerName(providerName); +                       +      } +       +      authnRequestBuilder.requesterId( +          generateRequesterId(pendingReq.getRawData(Constants.DATA_REQUESTERID, String.class))); +             +    } +  } +   +  /** +   * Build LoA based on Service-Provider configuration. +   *  +   * @param spConfig Current SP configuration +   * @param authnRequestBuilder AuthnRequest builder +   */ +  protected void buildLevelOfAssurance(ISpConfiguration spConfig, Builder authnRequestBuilder) { +    // TODO: set matching mode if eIDAS ref. impl. support this method + +    // TODO: update if eIDAS ref. impl. supports exact matching for non-notified LoA +    // schemes +    String loa = EaafConstants.EIDAS_LOA_HIGH; +    if (spConfig.getRequiredLoA() != null) { +      if (spConfig.getRequiredLoA().isEmpty()) { +        log.info("No eIDAS LoA requested. Use LoA HIGH as default"); +      } else { +        if (spConfig.getRequiredLoA().size() > 1) { +          log.info( +              "Currently only ONE requested LoA is supported for service provider. Use first one ... "); +        } + +        loa = spConfig.getRequiredLoA().get(0); + +      } +    } + +    log.debug("Request eIdAS node with LoA: " + loa); +    authnRequestBuilder.levelsOfAssuranceValues(Arrays.asList(loa)); +     +  } +   +  private String generateRequesterId(String requesterId) { +    if (requesterId != null && basicConfig.getBasicConfigurationBoolean( +        Constants.CONIG_PROPS_EIDAS_NODE_REQUESTERID_USE_HASHED_VERSION, true)) {             +      try { +        log.trace("Building hashed 'requesterId' for private SP ... "); +        MessageDigest digest = MessageDigest.getInstance("SHA-256"); +        String encodedRequesterId = Base64.getEncoder().encodeToString( +            digest.digest(requesterId.getBytes(StandardCharsets.UTF_8)));                 +        log.debug("Set 'requesterId' for: {} to: {}", requesterId, encodedRequesterId); +        return encodedRequesterId; +         +      } catch (NoSuchAlgorithmException e) { +        log.error("Can NOT generate hashed 'requesterId' from: {}. Use it as it is", requesterId, e); +         +      } +             +    } +     +    return requesterId; +     +  } + + +  private void buildRequestedAttributes(Builder authnRequestBuilder) { +    // build and add requested attribute set +    final Map<String, Boolean> ccSpecificReqAttr = getCountrySpecificRequestedAttributes(); +    log.debug("Get #{} country-specific requested attributes", ccSpecificReqAttr.size()); + +    final Map<String, Boolean> mdsReqAttr = attrRegistry.getDefaultAttributeSetFromConfiguration(); +    log.trace("Get #{} default requested attributes", mdsReqAttr.size()); + +    // put it together +    ccSpecificReqAttr.putAll(mdsReqAttr); + +    // convert it to eIDAS attributes +    final ImmutableAttributeMap reqAttrMap = translateToEidasAttributes(ccSpecificReqAttr); +    authnRequestBuilder.requestedAttributes(reqAttrMap); + +  } + +  private ImmutableAttributeMap translateToEidasAttributes(final Map<String, Boolean> requiredAttributes) { +    final ImmutableAttributeMap.Builder builder = ImmutableAttributeMap.builder(); +    for (final Map.Entry<String, Boolean> attribute : requiredAttributes.entrySet()) { +      final String name = attribute.getKey(); +      final ImmutableSortedSet<AttributeDefinition<?>> byFriendlyName = attrRegistry +          .getCoreAttributeRegistry().getByFriendlyName(name); +      if (!byFriendlyName.isEmpty()) { +        final AttributeDefinition<?> attributeDefinition = byFriendlyName.first(); +        builder.put(AttributeDefinition.builder(attributeDefinition).required(attribute.getValue()).build()); + +      } else { +        log.warn("Can NOT request UNKNOWN attribute: " + attribute.getKey() + " Ignore it!"); +      } + +    } + +    return builder.build(); + +  } +   +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/DeEidProcessor.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/DeEidProcessor.java new file mode 100644 index 00000000..6dc08181 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/DeEidProcessor.java @@ -0,0 +1,113 @@ +/* + * 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.handler; + +import java.io.UnsupportedEncodingException; +import java.util.Base64; +import java.util.Map; + +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.binary.Hex; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidPostProcessingException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.EidasResponseUtils; +import at.gv.egiz.eaaf.core.impl.data.Triple; + + +public class DeEidProcessor extends AbstractEidProcessor { +  private static final Logger log = LoggerFactory.getLogger(DeEidProcessor.class); +  private static final String canHandleCC = "DE"; + +  private int priority = 1; + +  @Override +  public int getPriority() { +    return priority; +  } + +  @Override +  public boolean canHandle(String countryCode) { +    return countryCode != null && countryCode.equalsIgnoreCase(canHandleCC); + +  } + +  public void setPriority(int priority) { +    this.priority = priority; +  } + +  @Override +  public String getName() { +    return "DE-PostProcessor"; + +  } + +  @Override +  protected String processPseudonym(Object uniqeIdentifierObj) throws EidPostProcessingException, +      EidasAttributeException { +    if (uniqeIdentifierObj == null || !(uniqeIdentifierObj instanceof String)) { +      throw new EidasAttributeException(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); +    } + +    final Triple<String, String, String> eIdentifier = +        EidasResponseUtils.parseEidasPersonalIdentifier((String) uniqeIdentifierObj); + +    log.trace(getName() + " starts processing of attribute: " + Constants.eIDAS_ATTR_PERSONALIDENTIFIER); +    final String result = convertDeIdentifier(eIdentifier.getThird()); +    log.debug(getName() + " finished processing of attribute: " + Constants.eIDAS_ATTR_PERSONALIDENTIFIER); + +    return result; + +  } + +  private String convertDeIdentifier(String hexEncodedDeIdentifier) throws EidPostProcessingException { +    if (hexEncodedDeIdentifier.length() != 64) { +      throw new EidPostProcessingException("ernb.03", new Object[] { +          "Input has wrong length, expected 64 chars" }); +    } + +    byte[] data; +    try { +      data = Hex.decodeHex(hexEncodedDeIdentifier); +      final byte[] encoded = Base64.getEncoder().encode(data); +      return new String(encoded, "UTF-8"); + +    } catch (final DecoderException | UnsupportedEncodingException e) { +      throw new EidPostProcessingException("ernb.03", null, e); + +    } + +     +  } + +  @Override +  protected Map<String, Boolean> getCountrySpecificRequestedAttributes() { +    return attrRegistry.getAttributeSetFromConfiguration(canHandleCC); + +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/GenericEidProcessor.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/GenericEidProcessor.java new file mode 100644 index 00000000..69949435 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/GenericEidProcessor.java @@ -0,0 +1,61 @@ +/* + * 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.handler; + +import java.util.HashMap; +import java.util.Map; + +public class GenericEidProcessor extends AbstractEidProcessor { + +  private int priority = 0; + +  @Override +  public int getPriority() { +    return priority; + +  } + +  @Override +  public boolean canHandle(String countryCode) { +    return true; + +  } + +  public void setPriority(int priority) { +    this.priority = priority; +  } + +  @Override +  public String getName() { +    return "Default-PostProcessor"; + +  } + +  @Override +  protected Map<String, Boolean> getCountrySpecificRequestedAttributes() { +    return new HashMap<>(); + +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/INationalEidProcessor.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/INationalEidProcessor.java new file mode 100644 index 00000000..577efbcd --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/INationalEidProcessor.java @@ -0,0 +1,81 @@ +/* + * 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.handler; + +import java.util.Map; + +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.EidPostProcessingException; +import at.gv.egiz.eaaf.core.api.IRequest; +import eu.eidas.auth.commons.light.ILightRequest; +import eu.eidas.auth.commons.light.impl.LightRequest.Builder; + +public interface INationalEidProcessor { + +  /** +   * Get a friendlyName of this post-processor implementation. +   *  +   * @return +   */ +  String getName(); + +  /** +   * Get the priority of this eID Post-Processor <br> +   * If more than one Post-Processor implementations can handle the eID data, the +   * post-processor with the highest priority are selected. The Default-Processor +   * has priority '0' +   *  +   * @return Priority of this handler +   */ +  int getPriority(); + +  /** +   * Check if this postProcessor is sensitive for a specific country. +   *  +   * @param countryCode of the eID data that should be processed +   * @return true if this implementation can handle the country, otherwise false +   *  +   */ +  boolean canHandle(String countryCode); + +  /** +   * Post-Process eIDAS eID data into national format. +   *  +   * @param eidasAttrMap Map of eIDAS attributes in format friendlyName and +   *                     attribute +   * @throws EidPostProcessingException In case of a post-processing error +   * @throws EidasAttributeException In case of an invalid eIDAS attribute +   */ +  ErnbEidData postProcess(Map<String, Object> eidasAttrMap) throws EidPostProcessingException, +      EidasAttributeException; + +  /** +   * Pre-Process eIDAS Request to national requirements. +   *  +   * @param pendingReq          current pending request +   * @param authnRequestBuilder eIDAS {@link ILightRequest} builder +   */ +  void preProcess(IRequest pendingReq, Builder authnRequestBuilder); +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/LuEidProcessor.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/LuEidProcessor.java new file mode 100644 index 00000000..8402457f --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/LuEidProcessor.java @@ -0,0 +1,61 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.handler; + +import java.util.HashMap; +import java.util.Map; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.gv.egiz.eaaf.core.api.IRequest; +import eu.eidas.auth.commons.light.impl.LightRequest.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class LuEidProcessor extends AbstractEidProcessor { + +   +   +  private static final String canHandleCC = "LU"; + +  @Getter +  @Setter +  private int priority = 1; +   +  @Override +  public String getName() { +    return "LU-PostProcessor"; +     +  } + +  @Override +  public boolean canHandle(String countryCode) { +    return countryCode != null && countryCode.equalsIgnoreCase(canHandleCC); +     +  } + +  @Override +  protected void buildProviderNameAndRequesterIdAttribute(IRequest pendingReq, Builder authnRequestBuilder) { +    super.buildProviderNameAndRequesterIdAttribute(pendingReq, authnRequestBuilder);     +    if (basicConfig.getBasicConfigurationBoolean( +        Constants.CONIG_PROPS_EIDAS_NODE_WORKAROUND_USE_STATIC_REQUESTERID_FOR_LUX, true)) {       +      String staticName = basicConfig.getBasicConfiguration( +          Constants.CONIG_PROPS_EIDAS_NODE_STATIC_PROVIDERNAME_FOR_PUBLIC_SP, +          Constants.DEFAULT_PROPS_EIDAS_NODE_STATIC_PROVIDERNAME_FOR_PUBLIC_SP);                 +      authnRequestBuilder.providerName(staticName); +      authnRequestBuilder.requesterId(staticName); +      log.debug("Use static name: {} as 'providerName' and 'RequesterId' for all 'LU' requests ", staticName); +                  +    } else {  +      log.info("Static 'providerName' and 'RequesterId' for country: LU is deactivated"); +       +    } +     +  } +   +  @Override +  protected Map<String, Boolean> getCountrySpecificRequestedAttributes() { +    return new HashMap<>(); +     +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/NlEidProcessor.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/NlEidProcessor.java new file mode 100644 index 00000000..2dd22927 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/NlEidProcessor.java @@ -0,0 +1,54 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.handler; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import at.gv.egiz.eaaf.core.api.data.EaafConstants; +import at.gv.egiz.eaaf.core.api.idp.ISpConfiguration; +import eu.eidas.auth.commons.light.impl.LightRequest.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class NlEidProcessor extends AbstractEidProcessor { + +   +   +  private static final String canHandleCC = "NL"; + +  @Getter +  @Setter +  private int priority = 1; +   +  @Override +  public String getName() { +    return "NL-PostProcessor"; +     +  } + +  @Override +  public boolean canHandle(String countryCode) { +    return countryCode != null && countryCode.equalsIgnoreCase(canHandleCC); +     +  } + +  protected void buildLevelOfAssurance(ISpConfiguration spConfig, Builder authnRequestBuilder) {     +    super.buildLevelOfAssurance(spConfig, authnRequestBuilder); +     +    //check requested level +    if (authnRequestBuilder.build().getLevelOfAssurance().equals(EaafConstants.EIDAS_LOA_LOW)) { +      log.debug("Upgrade LoA to {}, because NL needs it as minimum.", EaafConstants.EIDAS_LOA_SUBSTANTIAL); +      authnRequestBuilder.levelsOfAssuranceValues(Arrays.asList(EaafConstants.EIDAS_LOA_SUBSTANTIAL)); +       +    }         +  } +   +  @Override +  protected Map<String, Boolean> getCountrySpecificRequestedAttributes() { +    return new HashMap<>(); +     +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/AuthBlockSigningService.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/AuthBlockSigningService.java new file mode 100644 index 00000000..098e76ce --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/AuthBlockSigningService.java @@ -0,0 +1,211 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.service; + +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Provider; +import java.security.cert.X509Certificate; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.util.Base64; +import java.util.UUID; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang3.StringUtils; +import org.jose4j.lang.JoseException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; + +import at.asitplus.eidas.specific.core.MsEidasNodeConstants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.JoseUtils; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.exception.EaafKeyAccessException; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +/** + * Service to build and sign AuthBlock's for E-ID system. + *  + * @author tlenz + * + */ +@Slf4j +@Service("authBlockSigningService") +public class AuthBlockSigningService { + +  private static final String KEYSTORE_FRIENDLYNAME = "AuthBlock_Signing"; + +  private static ObjectMapper mapper = new ObjectMapper(); +   +  @Autowired +  IConfiguration basicConfig; +   +  @Autowired +  EaafKeyStoreFactory keyStoreFactory; + +   +  private Pair<KeyStore, Provider> keyStore; +   +  /** +   * Build and sign an AuthBlock for E-ID system.  +   *  +   * @param pendingReq data that should be added into AuthBlock +   * @return serialized JWS +   * @throws JsonProcessingException In case of a AuthBlock generation error  +   * @throws JoseException  In case of a JWS signing error +   * @throws EaafException  In case of a KeyStore or Key error +   */ +  public String buildSignedAuthBlock(IRequest pendingReq)  +      throws JsonProcessingException, EaafException, JoseException { +     +    //TODO: set Challenge to SAML2 requestId to create link between authentication request and authBlock +     +    // build AuthBlock +    EidasAuchBlock authBlock = new EidasAuchBlock(); +    authBlock.setChallenge(UUID.randomUUID().toString()); +    authBlock.setTimestamp(LocalDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.SECONDS)); +    authBlock.setUniqueId(pendingReq.getRawData(MsEidasNodeConstants.DATA_REQUESTERID, String.class));  +    authBlock.setPiiTransactionId(pendingReq.getUniquePiiTransactionIdentifier()); +     +    //set Binding PublicKey if available +    Object bindingPubKey = pendingReq.getRawData(MsEidasNodeConstants.EID_BINDING_PUBLIC_KEY_NAME); +    if (bindingPubKey instanceof String) { +      authBlock.setBindingPublicKey((String) bindingPubKey); +       +    } +     +    String jwsPayload = mapper.writeValueAsString(authBlock); +    log.debug("Building and sign authBlock with data: {}", jwsPayload); +     +    //sign JWS +    return JoseUtils +        .createSignature(keyStore, getKeyAlias(), getKeyPassword(), jwsPayload, false, +                         KEYSTORE_FRIENDLYNAME);     +  } +   + +  /** +   * Get the Base64 encoded PublicKey that is used to sign the AuthBlock. +   *  +   * @return Base64 encoded PublicKey +   * @throws EaafKeyAccessException In case of an unknown or invalid key +   */ +  public String getBase64EncodedPublicKey() throws EaafKeyAccessException {        +    Pair<Key, X509Certificate[]> keyPair = EaafKeyStoreUtils.getPrivateKeyAndCertificates( +        keyStore.getFirst(), getKeyAlias(), getKeyPassword(), true, KEYSTORE_FRIENDLYNAME);        +    return Base64.getEncoder().encodeToString(keyPair.getSecond()[0].getPublicKey().getEncoded()); +     +  } + +  @PostConstruct +  private void initialize() throws KeyStoreException, EaafException {    +    log.debug("Initializing AuthBlock signing service ... "); +    // read Connector wide config data TODO connector wide!    +    String keyStoreName = basicConfig +        .getBasicConfiguration(MsEidasNodeConstants.PROP_CONFIG_AUTHBLOCK_KEYSTORE_NAME); +    String keyStorePw = basicConfig +        .getBasicConfiguration(MsEidasNodeConstants.PROP_CONFIG_AUTHBLOCK_KEYSTORE_PASSWORD); +    String keyStorePath = basicConfig +        .getBasicConfiguration(MsEidasNodeConstants.PROP_CONFIG_AUTHBLOCK_KEYSTORE_PATH); +    String keyStoreType = basicConfig +        .getBasicConfiguration(MsEidasNodeConstants.PROP_CONFIG_AUTHBLOCK_KEYSTORE_TYPE); + +     +    //build new KeyStore configuration +    KeyStoreConfiguration keyStoreConfiguration = new KeyStoreConfiguration(); +    keyStoreConfiguration.setFriendlyName(KEYSTORE_FRIENDLYNAME); +     +    keyStoreConfiguration.setSoftKeyStoreFilePath(keyStorePath); +    keyStoreConfiguration.setSoftKeyStorePassword(keyStorePw); +    keyStoreConfiguration.setKeyStoreType(KeyStoreConfiguration.KeyStoreType.fromString(keyStoreType));     +    keyStoreConfiguration.setKeyStoreName(keyStoreName); +     +    //validate KeyStore configuration +    keyStoreConfiguration.validate(); +         +    //validate key alias +    if (StringUtils.isEmpty(getKeyAlias())) { +      throw new EaafConfigurationException("config.08",  +          new Object[] {MsEidasNodeConstants.PROP_CONFIG_AUTHBLOCK_KEY_ALIAS}); +       +    } +         +    //build new KeyStore based on configuration +    keyStore =  keyStoreFactory.buildNewKeyStore(keyStoreConfiguration); +     +    //check if Key is accessible +    EaafKeyStoreUtils.getPrivateKeyAndCertificates( +        keyStore.getFirst(), getKeyAlias(), getKeyPassword(), true, KEYSTORE_FRIENDLYNAME); +     +    log.info("AuthBlock signing-service successful initialized"); +     +  }    +   +  private char[] getKeyPassword() { +    final String value = basicConfig.getBasicConfiguration(MsEidasNodeConstants.PROP_CONFIG_AUTHBLOCK_KEY_PASSWORD); +    if (value != null) { +      return value.trim().toCharArray(); +    } + +    return null; +     +  } + + +  private String getKeyAlias() { +    return basicConfig +        .getBasicConfiguration(MsEidasNodeConstants.PROP_CONFIG_AUTHBLOCK_KEY_ALIAS); +     +  } +   +  /** +   * Technical AuthBlock for eIDAS Authentication. +   *  +   * @author tlenz +   * +   */ +  @Data +  @JsonInclude(JsonInclude.Include.NON_NULL) +  private static class EidasAuchBlock { + +    @JsonProperty("challenge") +    private String challenge; +     +    @JsonProperty("timestamp") +    @JsonSerialize(using = LocalDateTimeSerializer.class) +    @JsonDeserialize(using = LocalDateTimeDeserializer.class) +    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone = "UTC") +    private LocalDateTime timestamp; +     +    @JsonProperty("appId") +    private String uniqueId; +     +    @JsonProperty("piiTransactionId") +    private String piiTransactionId; +     +    @JsonProperty("bindingPublicKey") +    private String bindingPublicKey; +     +  } + +   +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/CcSpecificEidProcessingService.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/CcSpecificEidProcessingService.java new file mode 100644 index 00000000..230d6052 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/CcSpecificEidProcessingService.java @@ -0,0 +1,135 @@ +/* + * 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.service; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +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.stereotype.Service; + +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.EidPostProcessingException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.handler.INationalEidProcessor; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.EidasResponseUtils; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.impl.data.Triple; +import eu.eidas.auth.commons.light.impl.LightRequest.Builder; + +@Service +public class CcSpecificEidProcessingService implements ICcSpecificEidProcessingService { +  private static final Logger log = LoggerFactory.getLogger(CcSpecificEidProcessingService.class); + +  @Autowired +  private ApplicationContext context; + +  private final List<INationalEidProcessor> handlers = new ArrayList<>(); + +  @PostConstruct +  private void initialize() { +    log.debug("Initialize eID PostProcessing-Service ... "); +    final Map<String, INationalEidProcessor> postProcessors = context.getBeansOfType( +        INationalEidProcessor.class); +    final Iterator<Entry<String, INationalEidProcessor>> iterator = postProcessors.entrySet().iterator(); +    while (iterator.hasNext()) { +      final Entry<String, INationalEidProcessor> el = iterator.next(); +      log.debug("Find eID-PostProcessor with name: " + el.getKey()); +      handlers.add(el.getValue()); + +    } + +    log.trace("Sorting eID-PostProcessors on priority ... "); +    Collections.sort(handlers, (thisAuthModule, otherAuthModule) -> { +      final int thisOrder = thisAuthModule.getPriority(); +      final int otherOrder = otherAuthModule.getPriority(); +      return thisOrder < otherOrder ? 1 : thisOrder == otherOrder ? 0 : -1; +    }); + +    log.info("# " + handlers.size() + " eID PostProcessing services are registrated"); + +  } + +  @Override +  public void preProcess(String selectedCitizenCountry, IRequest pendingReq, Builder authnRequestBuilder) +      throws EidPostProcessingException { +    if (StringUtils.isEmpty(selectedCitizenCountry)) { +      log.info("No CountryCode for eID Pre-Processor. Default Pre-Processor will be used"); +    } + +    for (final INationalEidProcessor el : handlers) { +      if (el.canHandle(selectedCitizenCountry)) { +        log.debug("Pre-Process eIDAS request for " + selectedCitizenCountry + " by using:  " + el.getName()); +        el.preProcess(pendingReq, authnRequestBuilder); +        return; + +      } +    } + +    log.error("NO eID PostProcessor FOUND. Looks like a depentency problem!"); +    throw new EidPostProcessingException("internal.00", null); + +  } + +  @Override +  public ErnbEidData postProcess(Map<String, Object> eidasAttrMap) throws EidPostProcessingException, +      EidasAttributeException { +    // extract citizen country from eIDAS unique identifier +    final Object eIdentifierObj = eidasAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); +    if (eIdentifierObj == null || !(eIdentifierObj instanceof String)) { +      throw new EidasAttributeException(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); +    } + +    final Triple<String, String, String> eIdentifier = +        EidasResponseUtils.parseEidasPersonalIdentifier((String) eIdentifierObj); +    final String citizenCountry = eIdentifier.getFirst(); + +    if (StringUtils.isEmpty(citizenCountry)) { +      log.info("No CountryCode for eID PostProcessor. Default-PostProcessor will be used"); +    } + +    for (final INationalEidProcessor el : handlers) { +      if (el.canHandle(citizenCountry)) { +        log.debug("Post-Process eIDAS eID from " + citizenCountry + " by using:  " + el.getName()); +        return el.postProcess(eidasAttrMap); + +      } +    } + +    log.error("NO eID PostProcessor FOUND. Looks like a depentency problem!"); +    throw new EidPostProcessingException("internal.00", null); +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/EidasAttributeRegistry.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/EidasAttributeRegistry.java new file mode 100644 index 00000000..e73491ab --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/EidasAttributeRegistry.java @@ -0,0 +1,180 @@ +/* + * 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.service; + +import java.io.File; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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.lang.NonNull; +import org.springframework.stereotype.Service; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.gv.egiz.eaaf.core.api.idp.IConfigurationWithSP; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.impl.utils.KeyValueUtils; +import eu.eidas.auth.commons.attribute.AttributeRegistries; +import eu.eidas.auth.commons.attribute.AttributeRegistry; + +@Service("attributeRegistry") +public class EidasAttributeRegistry { +  private static final Logger log = LoggerFactory.getLogger(EidasAttributeRegistry.class); +  @Autowired +  private IConfigurationWithSP basicConfig; + +  private AttributeRegistry coreAttributeRegistry; + +  private String eidasAttributesFile; +  private String additionalAttributesFile; + +  @PostConstruct +  private void initialize() throws RuntimeException { +    try { +      if (eidasAttributesFile.isEmpty()) { +        log.error("Basic eIDAS addribute definition NOT defined"); +        throw new EaafConfigurationException("config.30", +            new Object[] { "eidas-attributes.xml" }); + +      } + +      boolean additionalAttrAvailabe = false; +      if (!additionalAttributesFile.isEmpty()) { +        final File file = new File(additionalAttributesFile); +        if (file.exists()) { +          additionalAttrAvailabe = true; +        } + +      } + +      if (!additionalAttrAvailabe) { +        log.info("Start eIDAS ref. impl. Core without additional eIDAS attribute definitions ... "); +        coreAttributeRegistry = AttributeRegistries.fromFiles(eidasAttributesFile, null); + +      } else { +        // load attribute definitions +        log.info("Start eIDAS ref. impl. Core with additional eIDAS attribute definitions ... "); +        coreAttributeRegistry = AttributeRegistries.fromFiles(eidasAttributesFile, null, +            additionalAttributesFile); + +      } + +    } catch (final Throwable e) { +      log.error("Can NOT initialize eIDAS attribute definition.", e); +      throw new RuntimeException("Can NOT initialize eIDAS attribute definition.", e); + +    } +  } + +  public AttributeRegistry getCoreAttributeRegistry() { +    return coreAttributeRegistry; +  } + +  /** +   * Get Map of attributes that are requested by default. +   *  +   * @return Map of AttributeIdentifier, isRequired flag +   */ +  @NonNull +  public Map<String, Boolean> getDefaultAttributeSetFromConfiguration() { +    /* +     * TODO: select set for representation if mandates should be used. It's an open +     * task in respect to requested eIDAS attributes and isRequired flag, because +     * there can be a decision problem in case of natural or legal person +     * representation! From an Austrian use-case point of view, an Austrian service +     * provider can support mandates for natural and legal persons at the same time. +     * However, we CAN NOT request attributes for natural AND legal persons on the +     * same time, because it's not possible to represent both simultaneously. +     */ +    final Map<String, String> configAttributes = +        basicConfig.getBasicConfigurationWithPrefix( +            Constants.CONIG_PROPS_EIDAS_NODE_ATTRIBUTES_REQUESTED_DEFAULT_ONLYNATURAL); +    return processAttributeInfosFromConfig(configAttributes); + +  } + +  /** +   * Get a Map of attributes that are additionally requested for a specific country. +   *  +   * @param countryCode Country Code +   * @return Map of AttributeIdentifier, isRequired flag +   */ +  @NonNull +  public Map<String, Boolean> getAttributeSetFromConfiguration(String countryCode) { + +    /* +     * TODO: select set for representation if mandates should be used. It's an open +     * task in respect to requested eIDAS attributes and isRequired flag, because +     * there can be a decision problem in case of natural or legal person +     * representation! From an Austrian use-case point of view, an Austrian service +     * provider can support mandates for natural and legal persons at the same time. +     * However, we CAN NOT request attributes for natural AND legal persons on the +     * same time, because it's not possible to represent both simultaneously. +     */ +    final Map<String, String> configAttributes = +        basicConfig.getBasicConfigurationWithPrefix( +            MessageFormat.format( +                Constants.CONIG_PROPS_EIDAS_NODE_ATTRIBUTES_REQUESTED_CC_SPECIFIC_ONLYNATURAL, +                countryCode.toLowerCase())); +    return processAttributeInfosFromConfig(configAttributes); + +  } + +  private Map<String, Boolean> processAttributeInfosFromConfig(Map<String, String> configAttributes) { + +    final Map<String, Boolean> result = new HashMap<>(); +    for (final String el : configAttributes.values()) { +      if (StringUtils.isNotEmpty(el.trim())) { +        final List<String> attrDef = KeyValueUtils.getListOfCsvValues(el.trim()); +        boolean isRequired = false; +        if (attrDef.size() == 2) { +          isRequired = Boolean.parseBoolean(attrDef.get(1)); +        } + +        result.put(attrDef.get(0), isRequired); + +      } +    } + +    log.trace("Load #" + result.size() + " requested attributes from configuration"); +    return result; + +  } + +  public void setEidasAttributesFile(String eidasAttributesFile) { +    this.eidasAttributesFile = eidasAttributesFile; +  } + +  public void setAdditionalAttributesFile(String additionalAttributesFile) { +    this.additionalAttributesFile = additionalAttributesFile; +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/EidasDataStore.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/EidasDataStore.java new file mode 100644 index 00000000..549aa65c --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/EidasDataStore.java @@ -0,0 +1,363 @@ +/* + * 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.service; + +//import java.io.File; +//import java.io.IOException; +//import java.sql.Connection; +//import java.sql.DriverManager; +//import java.sql.PreparedStatement; +//import java.sql.ResultSet; +//import java.sql.SQLException; +//import java.sql.Statement; +//import java.time.Instant; +//import java.util.Properties; +// +//import javax.annotation.PostConstruct; +// +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.stereotype.Component; +//import org.sqlite.SQLiteConfig; +//import org.sqlite.SQLiteConfig.LockingMode; +//import org.sqlite.SQLiteConfig.SynchronousMode; +//import org.sqlite.SQLiteErrorCode; +// +//import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +//import at.asitplus.eidas.specific.modules.auth.eidas.v2.DAO.eIDASPersonalIdStoreDAO; +//import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.SQLiteServiceException; +//import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +//import at.gv.egiz.eaaf.core.impl.data.Pair; +//import at.gv.egiz.eaaf.core.impl.data.Trible; +// +//@Component +//@Deprecated +//public class EidasDataStore { +// +//  private static final String SQLITE_JDBC_DRIVER_CLASS = "org.sqlite.JDBC"; +//  private static final String SQLITE_CONNECTION_PARAM = "jdbc:sqlite:%s"; +//  private static final boolean sleep = true; +//  private static final int howLongToSleepOnBusyLock_ = 100; +// +//  private static final Logger log = LoggerFactory.getLogger(EidasDataStore.class); +// +//  @Autowired +//  private IConfiguration basicConfig; +// +//  private String connectionUrl; +//  private Connection conn = null; +// +//  @PostConstruct +//  private void initialize() throws SQLiteServiceException { +//    try { +//      final String sqlLiteDbUrl = basicConfig.getBasicConfiguration( +//          Constants.CONIG_PROPS_EIDAS_SZRCLIENT_WORKAROUND_SQLLITEDATASTORE_URL, +//          basicConfig.getConfigurationRootDirectory().toString() + "/sqlite/database.db" +// +//      ); +// +//      log.info("Use SQLite database with URL: " + sqlLiteDbUrl); +// +//      // check if SQLite lib is in Classpath +//      Class.forName(SQLITE_JDBC_DRIVER_CLASS); +// +//      // open DB connection +//      boolean isNewFileCreated = false; +// +//      // open file or create file if not already exists +//      final File dbFile = new File(sqlLiteDbUrl); +//      if (!dbFile.exists()) { +//        log.info("SQLite database does not exist. Creating new database file ... "); +//        dbFile.createNewFile(); +//        isNewFileCreated = true; +// +//      } +// +//      // open database connection +//      connectionUrl = String.format(SQLITE_CONNECTION_PARAM, dbFile.getPath()); +// +//      // create DB scheme if new DB file was created +//      if (isNewFileCreated) { +//        executeUpdate(startConnection().createStatement(), eIDASPersonalIdStoreDAO.CREATE); +//        log.debug("SQLite db scheme created"); +// +//      } +// +//    } catch (final ClassNotFoundException e) { +//      log.warn("Can NOT initialize SQLite database for temporarly identity mapping. ", e); +//      throw new SQLiteServiceException("internal.05", new Object[] { e.getMessage() }, e); +// +//    } catch (SQLException | IOException e) { +//      log.warn("Can NOT initialize SQLite database for temporarly identity mapping. ", e); +//      throw new SQLiteServiceException("internal.05", new Object[] { e.getMessage() }, e); +// +//    } +// +//  } +// +//  /** +//   * Store a mapping entry with eIDAS personal identifier (source country / +//   * destination country / personal identifier) and the identifier that is used +//   * for ERnB communication. +//   * +//   * @param transactionId Id of this authentication transaction +//   * @param eidasId       eIDAS personal identifier without country prefixes +//   * @param ernbId        personal identifier that is used to request the ERnB +//   * @throws SQLiteServiceException In case of a database error +//   */ +//  public void storeNationalId(String transactionId, Trible<String, String, String> eidasId, String ernbId) +//      throws SQLiteServiceException { +//    try { +//      final PreparedStatement preStatment = startConnection().prepareStatement( +//          eIDASPersonalIdStoreDAO.INSERT, +//          Statement.RETURN_GENERATED_KEYS); +// +//      for (int i = 1; i <= eIDASPersonalIdStoreDAO.TABLE_COLS.size(); i++) { +//        final Pair<String, eIDASPersonalIdStoreDAO.T> col = eIDASPersonalIdStoreDAO.TABLE_COLS.get(i - 1); +//        if (col.getFirst().equals(eIDASPersonalIdStoreDAO.COLS.timestamp.name())) { +//          preStatment.setDate(i, new java.sql.Date(Instant.now().toEpochMilli())); +//        } else if (col.getFirst().equals(eIDASPersonalIdStoreDAO.COLS.transactionId.name())) { +//          preStatment.setString(i, transactionId); +//        } else if (col.getFirst().equals(eIDASPersonalIdStoreDAO.COLS.eidasId.name())) { +//          preStatment.setString(i, eidasId.getThird()); +//        } else if (col.getFirst().equals(eIDASPersonalIdStoreDAO.COLS.eidasSourceCountry.name())) { +//          preStatment.setString(i, eidasId.getFirst()); +//        } else if (col.getFirst().equals(eIDASPersonalIdStoreDAO.COLS.eidasDestinationCountry.name())) { +//          preStatment.setString(i, eidasId.getSecond()); +//        } else if (col.getFirst().equals(eIDASPersonalIdStoreDAO.COLS.ernbId.name())) { +//          preStatment.setString(i, ernbId); +//        } else { +//          log.warn("SQLite table:" + eIDASPersonalIdStoreDAO.NAME + " contains no col with name:" + col +//              .getFirst()); +//        } +// +//      } +// +//      // execute SQL query +//      final int sqlResult = preStatment.executeUpdate(); +// +//      if (sqlResult != 1) { +//        log.warn("SQLite query execution FAILED!"); +//        throw new SQLiteServiceException("internal.06", new Object[] { "Queryresult is '-1'" }); +// +//      } +// +//    } catch (SQLiteServiceException | SQLException e) { +//      log.warn("SQLite query execution FAILED!", e); +//      throw new SQLiteServiceException("internal.05", new Object[] { e.getMessage() }, e); +// +//    } +// +//  } +// +//  /** +//   * Get the ERnB related national identifier from mapping database. +//   *  +//   * @param eidasId eIDAS related identifier +//   * @return Mapped ERnB identifier +//   * @throws SQLiteServiceException In case of a database error +//   */ +//  public String getErnbNationalId(Trible<String, String, String> eidasId) throws SQLiteServiceException { +//    try { +//      final PreparedStatement preStatment = startConnection().prepareStatement( +//          eIDASPersonalIdStoreDAO.SELECT_BY_EIDAS_RAW_ID, +//          Statement.RETURN_GENERATED_KEYS); +// +//      preStatment.setString(1, eidasId.getThird()); +//      preStatment.setString(2, eidasId.getFirst()); +// +//      final ResultSet rs = preStatment.executeQuery(); +// +//      if (!rs.next()) { +//        return null; +//      } else { +//        return rs.getString(eIDASPersonalIdStoreDAO.COLS.ernbId.name()); +//      } +// +//    } catch (SQLiteServiceException | SQLException e) { +//      log.warn("SQLite query execution FAILED!", e); +//      throw new SQLiteServiceException("internal.05", new Object[] { e.getMessage() }, e); +// +//    } +// +//  } +// +//  /** +//   * Get the eIDAS identifier from an ERnB identifier. +//   *  +//   * @param ernbId ERnB specific identifier +//   * @return eIDAS unqiue identifier +//   * @throws SQLiteServiceException In case of a database error +//   */ +//  public String getEidasRawNationalId(String ernbId) throws SQLiteServiceException { +//    try { +//      final PreparedStatement preStatment = startConnection().prepareStatement( +//          eIDASPersonalIdStoreDAO.SELECT_BY_ERNB_ID, +//          Statement.RETURN_GENERATED_KEYS); +// +//      preStatment.setString(1, ernbId); +// +//      final ResultSet rs = preStatment.executeQuery(); +// +//      if (!rs.next()) { +//        return null; +//      } else { +//        return rs.getString(eIDASPersonalIdStoreDAO.COLS.eidasId.name()); +//      } +// +//    } catch (SQLiteServiceException | SQLException e) { +//      log.warn("SQLite query execution FAILED!", e); +//      throw new SQLiteServiceException("internal.05", new Object[] { e.getMessage() }, e); +// +//    } +// +//  } +// +//  private Connection startConnection() throws SQLiteServiceException { +//    int i = howLongToSleepOnBusyLock_; +// +//    while (true) { +//      try { +//        if (conn == null) { +//          log.info("Initializing SQLite database with URL: " + connectionUrl + " ... "); +//          conn = DriverManager.getConnection(connectionUrl, getConnectionProperties()); +// +//        } else { +//          if (!conn.isValid(10)) { +//            log.info("SQLite connection is not valid any more --> restarting connection ..."); +//            conn.close(); +//            conn = DriverManager.getConnection(connectionUrl, getConnectionProperties()); +//          } +//        } +// +//        log.info("SQLite database connected"); +//        return conn; +// +//      } catch (final SQLException e) { +//        final String msg = e.getLocalizedMessage(); +//        if (isBusyLocked(e)) { +//          log.warn(msg, e); +//          try { +//            if (sleep) { +//              Thread.sleep(i++); +//            } +// +//          } catch (final InterruptedException e1) { +//            throw new SQLiteServiceException("internal.05", new Object[] { e1.getMessage() }, e1); +// +//          } +//          continue; +// +//        } +//        throw new SQLiteServiceException("internal.05", new Object[] { e.getMessage() }, e); +// +//      } +//    } +//  } +// +//  /* +//   * SQLite query code +//   */ +// +//  protected Properties getConnectionProperties() { +//    final SQLiteConfig config = new SQLiteConfig(); +//    config.enforceForeignKeys(true); +//    config.setCacheSize(8000); +//    config.setLockingMode(LockingMode.NORMAL); +//    config.setSharedCache(false); +//    config.setReadUncommited(true); +//    config.setSynchronous(SynchronousMode.NORMAL); +//    return config.toProperties(); +// +//  } +// +//  private int executeUpdate(Statement statement, String sql) throws SQLiteServiceException { +//    final int i = 10; +// +//    int rc = -1; +//    while (true) { +//      try { +//        rc = statement.executeUpdate(sql); +//        break; +// +//      } catch (final SQLException e) { +//        try { +//          if (executeUpdateError(e, i)) { +//            continue; +//          } else { +//            throw new SQLiteServiceException("internal.06", +//                new Object[] { e.getMessage() }, e); +//          } +// +//        } catch (final SQLiteServiceException e1) { +//          log.warn("\n" + sql + "\n" + e1.getMessage()); +//          throw e1; +// +//        } +//      } +//    } +// +//    return rc; +// +//  } +// +//  private boolean isBusyLocked(SQLException e) { +//    final int eC = e.getErrorCode(); +// +//    if (eC == SQLiteErrorCode.SQLITE_LOCKED.code +//        || eC == SQLiteErrorCode.SQLITE_BUSY.code) { +//      log.trace("SQLite db is busy looked"); +//      return true; +// +//    } +// +//    final String msg = e.getMessage(); +//    if (msg.contains("[SQLITE_LOCKED]") || msg.contains("[SQLITE_BUSY]")) { +//      log.trace("SQLite db is busy looked"); +//      return true; +//    } +// +//    return false; +//  } +// +//  private boolean executeUpdateError(SQLException e, int theadSleepCounter) throws SQLiteServiceException { +//    if (isBusyLocked(e)) { +//      try { +//        if (sleep) { +//          Thread.sleep(theadSleepCounter++); +//        } +// +//      } catch (final InterruptedException e1) { +//        throw new SQLiteServiceException("internal.05", new Object[] { e1.getMessage() }, e1); +// +//      } +// +//      return true; +//    } +// +//    return false; +// +//  } +//} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/ICcSpecificEidProcessingService.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/ICcSpecificEidProcessingService.java new file mode 100644 index 00000000..ebbc15e4 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/ICcSpecificEidProcessingService.java @@ -0,0 +1,61 @@ +/* + * 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.service; + +import java.util.Map; + +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.EidPostProcessingException; +import at.gv.egiz.eaaf.core.api.IRequest; +import eu.eidas.auth.commons.light.ILightRequest; +import eu.eidas.auth.commons.light.impl.LightRequest.Builder; + +public interface ICcSpecificEidProcessingService { + +  /** +   * Post-process eIDAS eID attributes into national format. +   *  +   * @param eidasAttrMap Map of eIDAS attributes in format friendlyName and +   *                     attribute +   *  +   * @return eID attributes for SZR request +   * @throws EidPostProcessingException In case of a post-processing error +   * @throws EidasAttributeException In case of an invalid eIDAS attribute value +   */ +  ErnbEidData postProcess(Map<String, Object> eidasAttrMap) throws EidPostProcessingException, +      EidasAttributeException; + +  /** +   * Pre Process eIDAS request into national requirements. +   *  +   * @param selectedCC          Citizen Country from selection +   * @param pendingReq          current pending request +   * @param authnRequestBuilder eIDAS {@link ILightRequest} builder +   * @throws EidPostProcessingException In case of a pre-processing error +   */ +  void preProcess(String selectedCC, IRequest pendingReq, Builder authnRequestBuilder) +      throws EidPostProcessingException; + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrClient.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrClient.java new file mode 100644 index 00000000..1f5837d6 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrClient.java @@ -0,0 +1,522 @@ +/* + * 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.szr; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URL; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; +import javax.xml.namespace.QName; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import javax.xml.ws.BindingProvider; +import javax.xml.ws.Dispatch; +import javax.xml.ws.handler.Handler; + +import org.apache.commons.lang3.StringUtils; +import org.apache.cxf.configuration.jsse.TLSClientParameters; +import org.apache.cxf.endpoint.Client; +import org.apache.cxf.frontend.ClientProxy; +import org.apache.cxf.jaxws.DispatchImpl; +import org.apache.cxf.transport.http.HTTPConduit; +import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; +import org.apache.xpath.XPathAPI; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +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.SzrCommunicationException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.LoggingHandler; +import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions; +import at.gv.egiz.eaaf.core.api.data.XmlNamespaceConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.impl.utils.DomUtils; +import at.gv.egiz.eaaf.core.impl.utils.FileUtils; +import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils; +import szrservices.GetBPK; +import szrservices.GetBPKResponse; +import szrservices.GetIdentityLinkEidas; +import szrservices.GetIdentityLinkEidasResponse; +import szrservices.IdentityLinkType; +import szrservices.JwsHeaderParam; +import szrservices.ObjectFactory; +import szrservices.PersonInfoType; +import szrservices.SZR; +import szrservices.SZRException_Exception; +import szrservices.SignContent; +import szrservices.SignContentEntry; +import szrservices.SignContentResponseType; + + +@Service("SZRClientForeIDAS") +public class SzrClient { +  private static final Logger log = LoggerFactory.getLogger(SzrClient.class); + +  private static final String CLIENT_DEFAULT = "DefaultClient"; +  private static final String CLIENT_RAW = "RawClient"; + +  private static final String ATTR_NAME_VSZ = "urn:eidgvat:attributes.vsz.value"; +  private static final String ATTR_NAME_PUBKEYS = "urn:eidgvat:attributes.user.pubkeys"; +  private static final String ATTR_NAME_STATUS = "urn:eidgvat:attributes.eid.status"; +  private static final String KEY_BC_BIND = "bcBindReq"; +  private static final String JOSE_HEADER_USERCERTPINNING_TYPE = "urn:at.gv.eid:bindtype"; +  private static final String JOSE_HEADER_USERCERTPINNING_EIDASBIND = "urn:at.gv.eid:eidasBind"; +  public static final String ATTR_NAME_MDS = "urn:eidgvat:mds"; +   +  @Autowired +  private IConfiguration basicConfig; + +  // client for anything, without identitylink +  private SZR szr = null; + +  // RAW client is needed for identitylink +  private Dispatch<Source> dispatch = null; + +  private SzrService szrService = null; +  private String szrUrl = null; +  private QName qname = null; + +  final ObjectMapper mapper = new ObjectMapper(); + +  /** +   * Get IdentityLink of a person. +   * +   * @param personInfo Person identification information +   * @return IdentityLink +   * @throws SzrCommunicationException In case of a SZR error +   */ +  public IdentityLinkType getIdentityLinkInRawMode(PersonInfoType personInfo) +      throws SzrCommunicationException { +    try { +      final GetIdentityLinkEidas getIdl = new GetIdentityLinkEidas(); +      getIdl.setPersonInfo(personInfo); + +      final JAXBContext jaxbContext = JAXBContext.newInstance(ObjectFactory.class); +      final Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); + +      final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); +      jaxbMarshaller.marshal(getIdl, outputStream); +      outputStream.flush(); + +      final Source source = new StreamSource(new ByteArrayInputStream(outputStream.toByteArray())); +      outputStream.close(); + +      log.trace("Requesting SZR ... "); +      final Source response = dispatch.invoke(source); +      log.trace("Receive RAW response from SZR"); + +      final byte[] szrResponse = sourceToByteArray(response); +      final GetIdentityLinkEidasResponse jaxbElement = (GetIdentityLinkEidasResponse) jaxbContext +          .createUnmarshaller().unmarshal(new ByteArrayInputStream(szrResponse)); + +      // build response +      log.trace(new String(szrResponse, "UTF-8")); + +      // ok, we have success +      final Document doc = DomUtils.parseDocument( +          new ByteArrayInputStream(szrResponse), +          true, +          XmlNamespaceConstants.ALL_SCHEMA_LOCATIONS + " " + Constants.SZR_SCHEMA_LOCATIONS, +          null, null); +      final String xpathExpression = "//saml:Assertion"; +      final Element nsNode = doc.createElementNS("urn:oasis:names:tc:SAML:1.0:assertion", "saml:NSNode"); + +      log.trace("Selecting signed doc " + xpathExpression); +      final Element documentNode = (Element) XPathAPI.selectSingleNode(doc, +          xpathExpression, nsNode); +      log.trace("Signed document: " + DomUtils.serializeNode(documentNode)); + +      final IdentityLinkType idl = new IdentityLinkType(); +      idl.setAssertion(documentNode); +      idl.setPersonInfo(jaxbElement.getGetIdentityLinkReturn().getPersonInfo()); + +      return idl; + +    } catch (final Exception e) { +      log.warn("SZR communication FAILED. Reason: " + e.getMessage(), e); +      throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); + +    } + +  } + +  /** +   * Get bPK of person. +   * +   * @param personInfo Person identification information +   * @param target     requested bPK target +   * @param vkz        Verfahrenskennzeichen +   * @return bPK for this person +   * @throws SzrCommunicationException In case of a SZR error +   */ +  public List<String> getBpk(PersonInfoType personInfo, String target, String vkz) +      throws SzrCommunicationException { +    try { +      final GetBPK parameters = new GetBPK(); +      parameters.setPersonInfo(personInfo); +      parameters.getBereichsKennung().add(target); +      parameters.setVKZ(vkz); +      final GetBPKResponse result = this.szr.getBPK(parameters); + +      return result.getGetBPKReturn(); + +    } catch (final SZRException_Exception e) { +      log.warn("SZR communication FAILED. Reason: " + e.getMessage(), e); +      throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); + +    } + +  } + +  /** +   * Request a encryped baseId from SRZ. +   * +   * @param personInfo Minimum dataset of person +   * @return encrypted baseId +   * @throws SzrCommunicationException    In case of a SZR error +   */ +  public String getEncryptedStammzahl(final PersonInfoType personInfo) +      throws SzrCommunicationException { + +    final String resp; +    try { +      resp = this.szr.getStammzahlEncrypted(personInfo, true); +    } catch (SZRException_Exception e) { +      throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); +    } + +    if (StringUtils.isEmpty(resp)) { +      throw new SzrCommunicationException("ernb.01", new Object[]{"Stammzahl response empty"}); // TODO error handling +    } + +    return resp; + +  } + +  /** +   * Sign an eidasBind data-structure that combines vsz with user's pubKey and E-ID status. +   * +   * @param vsz encryped baseId +   * @param bindingPubKey  binding PublikKey as PKCS1# (ASN.1) container +   * @param eidStatus Status of the E-ID +   * @param eidData eID information that was used for ERnP registration +   * @return bPK for this person +   * @throws SzrCommunicationException In case of a SZR error +   */ +  public String getEidsaBind(final String vsz, final String bindingPubKey, final String eidStatus, +      ErnbEidData eidData)throws SzrCommunicationException { + +    final Map<String, Object> eidsaBindMap = new HashMap<>(); +    eidsaBindMap.put(ATTR_NAME_VSZ, vsz); +    eidsaBindMap.put(ATTR_NAME_STATUS, eidStatus); +    eidsaBindMap.put(ATTR_NAME_PUBKEYS, Arrays.asList(bindingPubKey)); +    eidsaBindMap.put(PvpAttributeDefinitions.EID_ISSUING_NATION_NAME, eidData.getCitizenCountryCode()); +    injectMdsIfAvailableAndActive(eidsaBindMap, eidData);         +     +    try { +      final String serializedEidasBind = mapper.writeValueAsString(eidsaBindMap); +      final SignContent req = new SignContent(); +      final SignContentEntry eidasBindInfo = new SignContentEntry(); +      eidasBindInfo.setKey(KEY_BC_BIND); +      eidasBindInfo.setValue(serializedEidasBind); +      req.getIn().add(eidasBindInfo); +      req.setAppendCert(false); +      final JwsHeaderParam eidasBindJoseHeader = new JwsHeaderParam(); +      eidasBindJoseHeader.setKey(JOSE_HEADER_USERCERTPINNING_TYPE); +      eidasBindJoseHeader.setValue(JOSE_HEADER_USERCERTPINNING_EIDASBIND); +      req.getJWSHeaderParam().add(eidasBindJoseHeader); + +      log.trace("Requesting SZR to sign bcBind datastructure ... "); +      final SignContentResponseType resp = szr.signContent(req.isAppendCert(), req.getJWSHeaderParam(), req.getIn()); +      log.trace("Receive SZR response on bcBind siging operation "); + +      if (resp == null || resp.getOut() == null +          || resp.getOut().isEmpty() +          || StringUtils.isEmpty(resp.getOut().get(0).getValue())) { +        throw new SzrCommunicationException("ernb.01", new Object[]{"BcBind response empty"}); +      } + +      return resp.getOut().get(0).getValue(); + +    } catch (final JsonProcessingException | SZRException_Exception e) { +      log.warn("Requesting bcBind by using SZR FAILED. Reason: {}", e.getMessage(), null, e); +      throw new SzrCommunicationException("ernb.02", +          new Object[]{e.getMessage()}, e); +    } +  } + +  @PostConstruct +  private void initialize() { +    log.info("Starting SZR-Client initialization .... "); +    final URL url = SzrClient.class.getResource("/szr_client/SZR_v4.0.wsdl"); + +    final boolean useTestSzr = basicConfig.getBasicConfigurationBoolean( +        Constants.CONIG_PROPS_EIDAS_SZRCLIENT_USETESTSERVICE, +        true); + +    if (useTestSzr) { +      log.debug("Initializing SZR test environment configuration."); +      qname = SzrService.SZRTestumgebung; +      szrService = new SzrService(url, new QName("urn:SZRServices", "SZRService")); +      szr = szrService.getSzrTestumgebung(); +      szrUrl = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_ENDPOINT_TEST); + +    } else { +      log.debug("Initializing SZR productive configuration."); +      qname = SzrService.SZRProduktionsumgebung; +      szrService = new SzrService(url, new QName("urn:SZRServices", "SZRService")); +      szr = szrService.getSzrProduktionsumgebung(); +      szrUrl = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_ENDPOINT_PROD); + +    } + +    // create raw client; +    dispatch = szrService.createDispatch(qname, Source.class, javax.xml.ws.Service.Mode.PAYLOAD); + +    if (StringUtils.isEmpty(szrUrl)) { +      log.error("No SZR service-URL found. SZR-Client initalisiation failed."); +      throw new RuntimeException("No SZR service URL found. SZR-Client initalisiation failed."); + +    } + +    // check if Clients can be initialized +    if (szr == null) { +      log.error("SZR " + CLIENT_DEFAULT + " is 'NULL'. Something goes wrong"); +      throw new RuntimeException("SZR " + CLIENT_DEFAULT + " is 'NULL'. Something goes wrong"); + +    } +    if (dispatch == null) { +      log.error("SZR " + CLIENT_RAW + " is 'NULL'. Something goes wrong"); +      throw new RuntimeException("SZR " + CLIENT_RAW + " is 'NULL'. Something goes wrong"); + +    } + +    // inject handler +    log.info("Use SZR service-URL: " + szrUrl); +    injectBindingProvider((BindingProvider) szr, CLIENT_DEFAULT); +    injectBindingProvider(dispatch, CLIENT_RAW); + +    // inject http parameters and SSL context +    log.debug("Inject HTTP client settings ... "); +    injectHttpClient(szr, CLIENT_DEFAULT); +    injectHttpClient(dispatch, CLIENT_RAW); + +    log.info("SZR-Client initialization successfull"); +  } + +  private void injectHttpClient(Object raw, String clientType) { +    // extract client from implementation +    Client client = null; +    if (raw instanceof DispatchImpl<?>) { +      client = ((DispatchImpl<?>) raw).getClient(); +    } else if (raw instanceof Client) { +      client = ClientProxy.getClient(raw); +    } else { +      throw new RuntimeException("SOAP Client for SZR connection is of UNSUPPORTED type: " + raw.getClass() +          .getName()); +    } + +    // set basic connection policies +    final HTTPConduit http = (HTTPConduit) client.getConduit(); + +    // set timeout policy +    final HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy(); +    httpClientPolicy.setConnectionTimeout( +        Integer.parseInt(basicConfig.getBasicConfiguration( +            Constants.CONIG_PROPS_EIDAS_SZRCLIENT_TIMEOUT_CONNECTION, +            Constants.HTTP_CLIENT_DEFAULT_TIMEOUT_CONNECTION)) * 1000); +    httpClientPolicy.setReceiveTimeout( +        Integer.parseInt(basicConfig.getBasicConfiguration( +            Constants.CONIG_PROPS_EIDAS_SZRCLIENT_TIMEOUT_RESPONSE, +            Constants.HTTP_CLIENT_DEFAULT_TIMEOUT_RESPONSE)) * 1000); +    http.setClient(httpClientPolicy); + +    // inject SSL context in case of https +    if (szrUrl.toLowerCase().startsWith("https")) { +      log.debug("Adding SSLContext to client: " + clientType + " ... "); +      final TLSClientParameters tlsParams = new TLSClientParameters(); +      tlsParams.setSSLSocketFactory(createSslContext(clientType).getSocketFactory()); +      http.setTlsClientParameters(tlsParams); +      log.info("SSLContext initialized for client: " + clientType); + +    } + +  } + +  private void injectBindingProvider(BindingProvider bindingProvider, String clientType) { +    final Map<String, Object> requestContext = bindingProvider.getRequestContext(); +    requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, szrUrl); + +    log.trace("Adding JAX-WS request/response trace handler to client: " + clientType); +    List<Handler> handlerList = bindingProvider.getBinding().getHandlerChain(); +    if (handlerList == null) { +      handlerList = new ArrayList<>(); +      bindingProvider.getBinding().setHandlerChain(handlerList); + +    } + +    // add logging handler to trace messages if required +    if (basicConfig.getBasicConfigurationBoolean( +        Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_TRACEMESSAGES, +        false)) { +      final LoggingHandler loggingHandler = new LoggingHandler(); +      handlerList.add(loggingHandler); + +    } +    bindingProvider.getBinding().setHandlerChain(handlerList); +  } + +  private SSLContext createSslContext(String clientType) { +    try { +      final SSLContext context = SSLContext.getInstance("TLS"); + +      // initialize key-mangager for SSL client-authentication +      KeyManager[] keyManager = null; +      final String keyStorePath = basicConfig.getBasicConfiguration( +          Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_PATH); +      final String keyStorePassword = basicConfig.getBasicConfiguration( +          Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_PASSWORD); +      if (StringUtils.isNotEmpty(keyStorePath)) { +        log.trace("Find keyStore path: " + keyStorePath + " Injecting SSL client certificate ... "); +        try { +          final KeyStore keyStore = KeyStoreUtils.loadKeyStore( +              FileUtils.makeAbsoluteUrl(keyStorePath, basicConfig.getConfigurationRootDirectory()), +              keyStorePassword); + +          final KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); +          kmf.init(keyStore, keyStorePassword.toCharArray()); +          keyManager = kmf.getKeyManagers(); +          log.debug("SSL client certificate injected to client: " + clientType); + +        } catch (KeyStoreException | IOException | UnrecoverableKeyException e) { +          log.error("Can NOT load SSL client certificate from path: " + keyStorePath); +          throw new RuntimeException("Can NOT load SSL client certificate from path: " + keyStorePath, e); + +        } +      } else { +        log.debug( +            "No KeyStore for SSL Client Auth. found. Initializing SSLContext without authentication ... "); + +      } + +      // initialize SSL TrustStore +      TrustManager[] trustManager = null; +      final String trustStorePath = basicConfig.getBasicConfiguration( +          Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_PATH); +      final String trustStorePassword = basicConfig.getBasicConfiguration( +          Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_PASSWORD); +      if (StringUtils.isNotEmpty(trustStorePath)) { +        log.trace("Find trustStore path: " + trustStorePath + " Injecting SSL TrustStore ... "); +        try { +          final KeyStore trustStore = KeyStoreUtils.loadKeyStore( +              FileUtils.makeAbsoluteUrl(trustStorePath, basicConfig.getConfigurationRootDirectory()), +              trustStorePassword); + +          final TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); +          tmf.init(trustStore); +          trustManager = tmf.getTrustManagers(); +          log.debug("SSL TrustStore injected to client: " + clientType); + +        } catch (KeyStoreException | IOException e) { +          log.error("Can NOT open SSL TrustStore from path: " + trustStorePath); +          throw new RuntimeException("Can NOT open SSL TrustStore from path: " + trustStorePath, e); + +        } + +      } else { +        log.debug("No custom SSL TrustStore found. Initializing SSLContext with JVM default truststore ... "); + +      } + +      context.init(keyManager, trustManager, new SecureRandom()); +      return context; + +    } catch (NoSuchAlgorithmException | KeyManagementException e) { +      log.error("SSLContext initialization FAILED.", e); +      throw new RuntimeException("SSLContext initialization FAILED.", e); + +    } + +  } + +  private void injectMdsIfAvailableAndActive(Map<String, Object> eidsaBindMap, ErnbEidData eidData) { +    if (basicConfig.getBasicConfigurationBoolean( +        Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SET_MDS_TO_EIDASBIND, false)) { +      log.info("Injecting MDS into eidasBind ... "); +      final Map<String, Object> mds = new HashMap<>();       +      mds.put(PvpAttributeDefinitions.PRINCIPAL_NAME_NAME, eidData.getFamilyName()); +      mds.put(PvpAttributeDefinitions.GIVEN_NAME_NAME, eidData.getGivenName()); +      mds.put(PvpAttributeDefinitions.BIRTHDATE_NAME, eidData.getFormatedDateOfBirth());      +      eidsaBindMap.put(ATTR_NAME_MDS, mds); +       +    } +  } +   +  private byte[] sourceToByteArray(Source result) throws TransformerException { +    final TransformerFactory factory = TransformerFactory.newInstance(); +    factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); +    final Transformer transformer = factory.newTransformer(); +    transformer.setOutputProperty("omit-xml-declaration", "yes"); +    transformer.setOutputProperty("method", "xml"); +    final ByteArrayOutputStream out = new ByteArrayOutputStream(); +    final StreamResult streamResult = new StreamResult(); +    streamResult.setOutputStream(out); +    transformer.transform(result, streamResult); +    return out.toByteArray(); +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrService.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrService.java new file mode 100644 index 00000000..dde868b1 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrService.java @@ -0,0 +1,164 @@ +/* + * 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.szr; + +import java.net.URL; + +import javax.xml.namespace.QName; +import javax.xml.ws.Service; +import javax.xml.ws.WebEndpoint; +import javax.xml.ws.WebServiceClient; +import javax.xml.ws.WebServiceFeature; + +import szrservices.SZR; + +/** + * This class was generated by Apache CXF 3.1.16 2018-07-10T09:36:01.466+02:00 + * Generated source version: 3.1.16 + * + */ +@WebServiceClient(name = "SZRService", +    wsdlLocation = "./src/main/resources/szr_client/SZR-1.WSDL", +    targetNamespace = "urn:SZRServices") +public class SzrService extends Service { + +  public static final URL WSDL_LOCATION; + +  public static final QName SERVICE = new QName("urn:SZRServices", "SZRService"); +  public static final QName SZRProduktionsumgebung = new QName("urn:SZRServices", "SZRProduktionsumgebung"); +  public static final QName SZRTestumgebung = new QName("urn:SZRServices", "SZRTestumgebung"); +  public static final QName SZRBusinesspartnerTestumgebung = new QName("urn:SZRServices", +      "SZRBusinesspartnerTestumgebung"); +   +  static { +    URL url = SzrService.class.getResource("./src/main/resources/szr_client/SZR-1.WSDL"); +    if (url == null) { +      url = SzrService.class.getClassLoader().getResource("/szr_client/SZR-1.WSDL"); +    } +    if (url == null) { +      java.util.logging.Logger.getLogger(SzrService.class.getName()) +          .log(java.util.logging.Level.INFO, +              "Can not initialize the default wsdl from {0}", "/szr_client/SZR-1.WSDL"); +    } +    WSDL_LOCATION = url; + +  } + +  public SzrService(URL wsdlLocation) { +    super(wsdlLocation, SERVICE); +  } + +  public SzrService(URL wsdlLocation, QName serviceName) { +    super(wsdlLocation, serviceName); +  } + +  public SzrService() { +    super(WSDL_LOCATION, SERVICE); +  } + +  public SzrService(WebServiceFeature... features) { +    super(WSDL_LOCATION, SERVICE, features); +  } + +  public SzrService(URL wsdlLocation, WebServiceFeature... features) { +    super(wsdlLocation, SERVICE, features); +  } + +  public SzrService(URL wsdlLocation, QName serviceName, WebServiceFeature... features) { +    super(wsdlLocation, serviceName, features); +  } + +  /** +   * Get SZR Web-Service. +   * +   * @return returns SZR +   */ +  @WebEndpoint(name = "SZRProduktionsumgebung") +  public SZR getSzrProduktionsumgebung() { +    return super.getPort(SZRProduktionsumgebung, SZR.class); +  } + +  /** +   * Get SZR Web-Service. +   *  +   * @param features A list of {@link javax.xml.ws.WebServiceFeature} to configure +   *                 on the proxy. Supported features not in the +   *                 <code>features</code> parameter will have their default +   *                 values. +   * @return returns SZR +   */ +  @WebEndpoint(name = "SZRProduktionsumgebung") +  public SZR getSzrProduktionsumgebung(WebServiceFeature... features) { +    return super.getPort(SZRProduktionsumgebung, SZR.class, features); +  } + +  /** +   *Get SZR Web-Service. +   * +   * @return returns SZR +   */ +  @WebEndpoint(name = "SZRTestumgebung") +  public SZR getSzrTestumgebung() { +    return super.getPort(SZRTestumgebung, SZR.class); +  } + +  /** +   * Get SZR Web-Service. +   *  +   * @param features A list of {@link javax.xml.ws.WebServiceFeature} to configure +   *                 on the proxy. Supported features not in the +   *                 <code>features</code> parameter will have their default +   *                 values. +   * @return returns SZR +   */ +  @WebEndpoint(name = "SZRTestumgebung") +  public SZR getSzrTestumgebung(WebServiceFeature... features) { +    return super.getPort(SZRTestumgebung, SZR.class, features); +  } + +  /** +   * Get SZR Web-Service. +   * +   * @return returns SZR +   */ +  @WebEndpoint(name = "SZRBusinesspartnerTestumgebung") +  public SZR getSzrBusinesspartnerTestumgebung() { +    return super.getPort(SZRBusinesspartnerTestumgebung, SZR.class); +  } + +  /** +   * Get SZR Web-Service. +   *  +   * @param features A list of {@link javax.xml.ws.WebServiceFeature} to configure +   *                 on the proxy. Supported features not in the +   *                 <code>features</code> parameter will have their default +   *                 values. +   * @return returns SZR +   */ +  @WebEndpoint(name = "SZRBusinesspartnerTestumgebung") +  public SZR getSzrBusinesspartnerTestumgebung(WebServiceFeature... features) { +    return super.getPort(SZRBusinesspartnerTestumgebung, SZR.class, features); +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateIdentityLinkTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateIdentityLinkTask.java new file mode 100644 index 00000000..6b1b96de --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateIdentityLinkTask.java @@ -0,0 +1,503 @@ +/* + * 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.core.MsConnectorEventCodes; +import at.asitplus.eidas.specific.core.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.builder.BpkBuilder; +import at.gv.egiz.eaaf.core.impl.data.Pair; +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<String, Object> 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); +                     +          //write revision-Log entry and extended infos personal-identifier mapping +          revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_VSZ_RECEIVED); +          writeExtendedRevisionLogEntry(simpleAttrMap, eidData); +           +           +          // get eIDAS bind +          String signedEidasBind = szrClient.getEidsaBind(vsz,  +              authBlockSigner.getBase64EncodedPublicKey(),  +              EID_STATUS, eidData); +          revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_EIDASBIND_RECEIVED); +          authProcessData.setGenericDataToSession(MsEidasNodeConstants.AUTH_DATA_EIDAS_BIND, signedEidasBind); +           +          //get signed AuthBlock +          String jwsSignature = authBlockSigner.buildSignedAuthBlock(pendingReq); +          revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.TECH_AUCHBLOCK_CREATED); +          authProcessData.setGenericDataToSession(MsEidasNodeConstants.AUTH_DATA_SZR_AUTHBLOCK, jwsSignature); +           +          //inject personal-data into session +          authProcessData.setEidProcess(true); +                +        } else { +          //request SZR +          SzrResultHolder idlResult = requestSzrForIdentityLink(personInfo); +           +          //write revision-Log entry for personal-identifier mapping +          writeExtendedRevisionLogEntry(simpleAttrMap, eidData); +           +          //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 void writeExtendedRevisionLogEntry(Map<String, Object> simpleAttrMap, ErnbEidData eidData) { +    // 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()); + +    } +  } +   +  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 = null; +    if (basicConfig +        .getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_USESRZFORBPKGENERATION, true)) { +      List<String> bpkList = szrClient +          .getBpk(personInfo, pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier(), +                  basicConfig +                      .getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_VKZ, "no VKZ defined")); +      if (!bpkList.isEmpty()) { +        bpk = bpkList.get(0); +         +      } +         + +    } else { +      log.debug("Calculating bPK from baseId ... "); +      new BpkBuilder(); +      final Pair<String, String> 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<String, Object> convertEidasAttrToSimpleMap( +      ImmutableMap<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>> attributeMap) { +    final Map<String, Object> 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<String> natPersonIdObj = EidasResponseUtils +            .translateStringListAttribute(el, attributeMap.get(el)); +        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. +   *  +   * <br><br> +   * <b>FOR LOCAL TESTING ONLY!!!</b> +   *  +   * @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<String, String> bpkCalc = BpkBuilder +        .generateAreaSpecificPersonIdentifier(identityLink.getIdentificationValue(), +                                              identityLink.getIdentificationType(), +                                              pendingReq.getServiceProviderConfiguration() +                                                        .getAreaSpecificTargetIdentifier());    +    return new SzrResultHolder(identityLink, bpkCalc.getFirst()); +     +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java new file mode 100644 index 00000000..b43c1bc2 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java @@ -0,0 +1,255 @@ +/* + * 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.util.UUID; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import at.asitplus.eidas.specific.core.MsConnectorEventCodes; +import at.asitplus.eidas.specific.core.MsEidasNodeConstants; +import at.asitplus.eidas.specific.core.gui.StaticGuiBuilderConfiguration; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasSAuthenticationException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.ICcSpecificEidProcessingService; +import at.gv.egiz.eaaf.core.api.gui.ISpringMvcGuiFormBuilder; +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.storage.ITransactionStorage; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; +import eu.eidas.auth.commons.EidasParameterKeys; +import eu.eidas.auth.commons.light.ILightRequest; +import eu.eidas.auth.commons.light.impl.LightRequest; +import eu.eidas.auth.commons.tx.BinaryLightToken; +import eu.eidas.specificcommunication.BinaryLightTokenHelper; +import eu.eidas.specificcommunication.SpecificCommunicationDefinitionBeanNames; +import eu.eidas.specificcommunication.exception.SpecificCommunicationException; +import eu.eidas.specificcommunication.protocol.SpecificCommunicationService; +import lombok.extern.slf4j.Slf4j; + +/** + * Authentication-process task that generates the Authn. Request to eIDAS Node. + *  + * @author tlenz + * + */ +@Slf4j +@Component("ConnecteIDASNodeTask") +public class GenerateAuthnRequestTask extends AbstractAuthServletTask { + +  @Autowired +  IConfiguration basicConfig; +  @Autowired +  ApplicationContext context; +  @Autowired +  ITransactionStorage transactionStore; +  @Autowired +  ISpringMvcGuiFormBuilder guiBuilder; +  @Autowired +  ICcSpecificEidProcessingService ccSpecificProcessing; + +  @Override +  public void execute(ExecutionContext executionContext, +      HttpServletRequest request, HttpServletResponse response) +      throws TaskExecutionException { + +    try { +      // get target, environment and validate citizen countryCode +      final String citizenCountryCode = (String) executionContext.get( +          MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY); +      final String environment = (String) executionContext.get( +          MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT); + +      if (StringUtils.isEmpty(citizenCountryCode)) { +        // illegal state; task should not have been executed without a selected country +        throw new EidasSAuthenticationException("eidas.03", new Object[] { "" }); + +      } + +      // TODO: maybe add countryCode validation before request ref. impl. eIDAS node +      log.info("Request eIDAS auth. for citizen of country: " + citizenCountryCode); +      revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.COUNTRY_SELECTED, citizenCountryCode); + +      // build eIDAS AuthnRequest +      final LightRequest.Builder authnRequestBuilder = LightRequest.builder(); +      authnRequestBuilder.id(UUID.randomUUID().toString()); + +      // set nameIDFormat +      authnRequestBuilder.nameIdFormat( +          authConfig.getBasicConfiguration(Constants.CONFIG_PROP_EIDAS_NODE_NAMEIDFORMAT)); + +      // set citizen country code for foreign uses +      authnRequestBuilder.citizenCountryCode(citizenCountryCode); +       +      //set Issuer +      final String issur = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_ENTITYID); +      if (StringUtils.isEmpty(issur)) { +        log.error("Found NO 'eIDAS node issuer' in configuration. Authentication NOT possible!"); +        throw new EaafConfigurationException("config.27", +            new Object[] { "Application config containts NO " + Constants.CONIG_PROPS_EIDAS_NODE_ENTITYID }); + +      } +      authnRequestBuilder.issuer(issur); + +       +      // Add country-specific informations into eIDAS request +      ccSpecificProcessing.preProcess(citizenCountryCode, pendingReq, authnRequestBuilder); + +      // build request +      final LightRequest lightAuthnReq = authnRequestBuilder.build(); + +      // put request into Hazelcast cache +      final BinaryLightToken token = putRequestInCommunicationCache(lightAuthnReq); +      final String tokenBase64 = BinaryLightTokenHelper.encodeBinaryLightTokenBase64(token); + +      // Workaround, because eIDAS node ref. impl. does not return relayState +      if (basicConfig.getBasicConfigurationBoolean( +          Constants.CONIG_PROPS_EIDAS_NODE_WORKAROUND_USEREQUESTIDASTRANSACTIONIDENTIFIER, +          false)) { +        log.trace("Put lightRequestId into transactionstore as session-handling backup"); +        transactionStore.put(lightAuthnReq.getId(), pendingReq.getPendingRequestId(), -1); + +      } + +      // select forward URL regarding the selected environment +      String forwardUrl = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_CONNECTOR_NODE_FORWARD_URL); +      if (StringUtils.isNotEmpty(environment)) { +        forwardUrl = selectedForwardUrlForEnvironment(environment); +      } + +      if (StringUtils.isEmpty(forwardUrl)) { +        log.warn("NO ForwardURL defined in configuration. Can NOT forward to eIDAS node! Process stops"); +        throw new EaafConfigurationException("config.08", new Object[] { +            environment == null ? Constants.CONIG_PROPS_EIDAS_CONNECTOR_NODE_FORWARD_URL +                : Constants.CONIG_PROPS_EIDAS_CONNECTOR_NODE_FORWARD_URL + "." + environment +        }); + +      } +      log.debug("ForwardURL: " + forwardUrl + " selected to forward eIDAS request"); + +      if (basicConfig.getBasicConfiguration( +          Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_METHOD, +          Constants.FORWARD_METHOD_GET).equals(Constants.FORWARD_METHOD_GET)) { + +        log.debug("Use http-redirect for eIDAS node forwarding ...  "); +        // send redirect +        final UriComponentsBuilder redirectUrl = UriComponentsBuilder.fromHttpUrl(forwardUrl); +        redirectUrl.queryParam(EidasParameterKeys.TOKEN.toString(), tokenBase64); +        response.sendRedirect(redirectUrl.build().encode().toString()); + +      } else { +        log.debug("Use http-post for eIDAS node forwarding ...  "); +        final StaticGuiBuilderConfiguration config = new StaticGuiBuilderConfiguration( +            basicConfig, +            pendingReq, +            Constants.TEMPLATE_POST_FORWARD_NAME, +            null, +            resourceLoader); + +        config.putCustomParameter(null, Constants.TEMPLATE_POST_FORWARD_ENDPOINT, forwardUrl); +        config.putCustomParameter(null, Constants.TEMPLATE_POST_FORWARD_TOKEN_NAME, +            EidasParameterKeys.TOKEN.toString()); +        config.putCustomParameter(null, Constants.TEMPLATE_POST_FORWARD_TOKEN_VALUE, +            tokenBase64); + +        guiBuilder.build(request, response, config, "Forward to eIDASNode form"); + +      } + +      revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.EIDAS_NODE_CONNECTED, lightAuthnReq.getId()); + +    } catch (final EidasSAuthenticationException e) { +      throw new TaskExecutionException(pendingReq, "eIDAS AuthnRequest generation FAILED.", e); + +    } catch (final Exception e) { +      log.warn("eIDAS AuthnRequest generation FAILED.", e); +      throw new TaskExecutionException(pendingReq, e.getMessage(), e); + +    } + +  } + +  /** +   * Select a forward URL from configuration for a specific environment <br> +   * <br> +   * <b>Info: </b> This method is needed, because eIDAS Ref. Impl only supports +   * one countrycode on each instance. In consequence, more than one eIDAS Ref. +   * Impl nodes are required to support producation, testing, or QS stages for one +   * country by using one ms-specific eIDAS connector +   *  +   * @param environment Environment selector from CountrySlection page +   * @return +   */ +  private String selectedForwardUrlForEnvironment(String environment) { +    log.trace("Starting endpoint selection process for environment: " + environment + " ... "); +    if (environment.equalsIgnoreCase(MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_PRODUCTION)) { +      return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_CONNECTOR_NODE_FORWARD_URL); +    } else if (environment.equalsIgnoreCase(MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_QS)) { +      return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_CONNECTOR_NODE_FORWARD_URL +          + "." + MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_QS); +    } else if (environment.equalsIgnoreCase( +        MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_TESTING)) { +      return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_CONNECTOR_NODE_FORWARD_URL +          + "." + MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_TESTING); +    } else if (environment.equalsIgnoreCase( +        MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_DEVELOPMENT)) { +      return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_CONNECTOR_NODE_FORWARD_URL +          + "." + MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_DEVELOPMENT); +    } + +    log.info("Environment selector: " + environment + " is not supported"); +    return null; + +  } + +  private BinaryLightToken putRequestInCommunicationCache(ILightRequest lightRequest) +      throws ServletException { +    final BinaryLightToken binaryLightToken; +    try { +      final SpecificCommunicationService springManagedSpecificConnectorCommunicationService = +          (SpecificCommunicationService) context.getBean( +              SpecificCommunicationDefinitionBeanNames.SPECIFIC_CONNECTOR_COMMUNICATION_SERVICE.toString()); + +      binaryLightToken = springManagedSpecificConnectorCommunicationService.putRequest(lightRequest); + +    } catch (final SpecificCommunicationException e) { +      log.error("Unable to process specific request"); +      throw new ServletException(e); + +    } + +    return binaryLightToken; +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java new file mode 100644 index 00000000..8d5df99f --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java @@ -0,0 +1,130 @@ +/* + * 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 javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import at.asitplus.eidas.specific.core.MsConnectorEventCodes; +import at.asitplus.eidas.specific.core.MsEidasNodeConstants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasSAuthenticationException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.EidasAttributeRegistry; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.validator.EidasResponseValidator; +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.exceptions.EaafException; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.auth.data.EidAuthProcessDataWrapper; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; +import eu.eidas.auth.commons.light.ILightResponse; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component("ReceiveResponseFromeIDASNodeTask") +public class ReceiveAuthnResponseTask extends AbstractAuthServletTask { + +  @Autowired +  private IConfiguration basicConfig; +  @Autowired +  private EidasAttributeRegistry attrRegistry; + +  @Override +  public void execute(ExecutionContext executionContext, HttpServletRequest request, +      HttpServletResponse response) throws TaskExecutionException { +    try { +      final ILightResponse eidasResponse = (ILightResponse) request.getAttribute( +          Constants.DATA_FULL_EIDAS_RESPONSE); +      if (eidasResponse == null) { +        log.warn("NO eIDAS response-message found."); +        throw new EidasSAuthenticationException("eidas.01", null); + +      } + +      log.debug("Receive eIDAS response with RespId:" + eidasResponse.getId() + " for ReqId:" + eidasResponse +          .getInResponseToId()); +      log.trace("Full eIDAS-Resp: " + eidasResponse.toString()); +      revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE, eidasResponse +          .getId()); + +      // check response StatusCode +      if (!eidasResponse.getStatus().getStatusCode().equals(Constants.SUCCESS_URI)) { +        log.info("Receice eIDAS Response with StatusCode:" + eidasResponse.getStatus().getStatusCode() +            + " Subcode:" + eidasResponse.getStatus().getSubStatusCode() + " Msg:" + eidasResponse.getStatus() +                .getStatusMessage()); +        throw new EidasSAuthenticationException("eidas.02", new Object[] { eidasResponse.getStatus() +            .getStatusCode(), eidasResponse.getStatus().getStatusMessage() }); + +      } + +      // extract all Attributes from response + +      // ********************************************************** +      // ******* MS-specificresponse validation ********** +      // ********************************************************** +      final String spCountry = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_COUNTRYCODE, +          Constants.DEFAULT_MS_NODE_COUNTRY_CODE); +      final String citizenCountryCode = (String) executionContext.get( +          MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY); +      EidasResponseValidator.validateResponse(pendingReq, eidasResponse, spCountry, citizenCountryCode, +          attrRegistry); + +      // ********************************************************** +      // ******* Store resonse infos into session object ********** +      // ********************************************************** + +      // update MOA-Session data with received information +      log.debug("Store eIDAS response information into pending-request."); +      final EidAuthProcessDataWrapper authProcessData = pendingReq.getSessionData(EidAuthProcessDataWrapper.class); +      authProcessData.setQaaLevel(eidasResponse.getLevelOfAssurance()); +      authProcessData.setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE, eidasResponse); + +       +      //inject set flag to inject  +      authProcessData.setTestIdentity( +          basicConfig.getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_IS_TEST_IDENTITY, false)); +             +      // store MOA-session to database +      requestStoreage.storePendingRequest(pendingReq); + +      revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_VALID); + +    } catch (final EaafException e) { +      revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_NOT_VALID); +      throw new TaskExecutionException(pendingReq, "eIDAS Response processing FAILED.", e); + +    } catch (final Exception e) { +      log.warn("eIDAS Response processing FAILED.", e); +      revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_NOT_VALID); +      throw new TaskExecutionException(pendingReq, e.getMessage(), +          new EidasSAuthenticationException("eidas.05", new Object[] { e.getMessage() }, e)); + +    } + +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/EidasResponseUtils.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/EidasResponseUtils.java new file mode 100644 index 00000000..c8c5a069 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/EidasResponseUtils.java @@ -0,0 +1,179 @@ +/* + * 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.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.annotation.Nullable; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.gv.egiz.eaaf.core.impl.data.Triple; +import eu.eidas.auth.commons.attribute.AttributeDefinition; +import eu.eidas.auth.commons.attribute.AttributeValue; +import eu.eidas.auth.commons.attribute.AttributeValueMarshaller; +import eu.eidas.auth.commons.attribute.AttributeValueMarshallingException; +import eu.eidas.auth.commons.attribute.AttributeValueTransliterator; +import eu.eidas.auth.commons.protocol.eidas.impl.PostalAddress; + +public class EidasResponseUtils { +  private static final Logger log = LoggerFactory.getLogger(EidasResponseUtils.class); + +  public static final String PERSONALIDENIFIER_VALIDATION_PATTERN = "^[A-Z,a-z]{2}/[A-Z,a-z]{2}/.*"; + +  /** +   * Validate a eIDAS PersonalIdentifier attribute value This validation is done +   * according to eIDAS SAML Attribute Profile - Section 2.2.3 Unique Identifier +   *  +   * @param uniqueID eIDAS attribute value of a unique identifier +   * @return true if the uniqueID matches to eIDAS to Unique Identifier +   *         specification, otherwise false +   */ +  public static boolean validateEidasPersonalIdentifier(String uniqueID) { +    final Pattern pattern = Pattern.compile(PERSONALIDENIFIER_VALIDATION_PATTERN); +    final Matcher matcher = pattern.matcher(uniqueID); +    return matcher.matches(); + +  } + +  /** +   * Parse an eIDAS PersonalIdentifier attribute value into it components. This +   * processing is done according to eIDAS SAML Attribute Profile - Section 2.2.3 +   * Unique Identifier +   *  +   * @param uniqueID eIDAS attribute value of a unique identifier +   * @return {@link Trible} that contains: <br> +   *         First : citizen country <br> +   *         Second: destination country <br> +   *         Third : unique identifier <br> +   *         or null if the attribute value has a wrong format +   */ +  public static Triple<String, String, String> parseEidasPersonalIdentifier(String uniqueID) { +    if (!validateEidasPersonalIdentifier(uniqueID)) { +      log.error("eIDAS attribute value for {} looks wrong formated. Value: {}",  +          Constants.eIDAS_ATTR_PERSONALIDENTIFIER, uniqueID); +      return null; + +    } +    return Triple.newInstance(uniqueID.substring(0, 2), uniqueID.substring(3, 5), uniqueID.substring(6)); + +  } + +  /** +   * Get eIDAS attribute-values from eIDAS Node attributes.  +   *  +   * @param attributeDefinition eIDAS attribute definition +   * @param attributeValues Attributes from eIDAS response +   * @return Set of attribute values. If more then one value than the first value contains the 'Latin' value.  +   */ +  // TODO: check possible problem with nonLatinCharacters +  public static List<String> translateStringListAttribute(AttributeDefinition<?> attributeDefinition, +      ImmutableSet<? extends AttributeValue<?>> attributeValues) { +    final List<String> stringListAttribute = new ArrayList<>(); +    if (attributeValues != null) { +      final AttributeValueMarshaller<?> attributeValueMarshaller = attributeDefinition +          .getAttributeValueMarshaller(); +      for (final AttributeValue<?> attributeValue : attributeValues.asList()) { +        String valueString = null; +        try { +          valueString = attributeValueMarshaller.marshal((AttributeValue) attributeValue); + +          log.trace("Find attr: {} with value: {} nonLatinFlag: {} needTransliteration: {}", +              attributeDefinition.getFriendlyName(), attributeValue.toString(), +              attributeValue.isNonLatinScriptAlternateVersion(),  +              AttributeValueTransliterator.needsTransliteration(valueString)); + +          // if (attributeValue.isNonLatinScriptAlternateVersion()) { +          if (!AttributeValueTransliterator.needsTransliteration(valueString)) { +            stringListAttribute.add(0, valueString); + +          } else { +            log.trace("Find 'needsTransliteration' flag. Setting this value at last list element ... "); +            stringListAttribute.add(valueString); + +          } + +        } catch (final AttributeValueMarshallingException e) { +          throw new IllegalStateException(e); + +        } +      } + +      log.trace("Extract values: {} for attr: {}",  +          StringUtils.join(stringListAttribute, ","), attributeDefinition.getFriendlyName()); +       +    } else { +      log.info("Can not extract infos from 'null' attribute value"); +       +    } + +    return stringListAttribute; + +  } + +  /** +   * Convert eIDAS DateTime attribute to Java Object.  +   *  +   * @param attributeDefinition eIDAS attribute definition. +   * @param attributeValues eIDAS attribute value +   * @return +   */ +  @Nullable +  public static DateTime translateDateAttribute(AttributeDefinition<?> attributeDefinition, +      ImmutableList<? extends AttributeValue<?>> attributeValues) { +    if (attributeValues.size() != 0) { +      final AttributeValue<?> firstAttributeValue = attributeValues.get(0); +      return (DateTime) firstAttributeValue.getValue(); + +    } + +    return null; +  } + +  /** +   * Concert eIDAS Address attribute to Java object. +   *  +   * @param attributeDefinition eIDAS attribute definition +   * @param attributeValues eIDAS attribute value +   * @return +   */ +  @Nullable +  public static PostalAddress translateAddressAttribute(AttributeDefinition<?> attributeDefinition, +      ImmutableList<? extends AttributeValue<?>> attributeValues) { +    final AttributeValue<?> firstAttributeValue = attributeValues.get(0); +    return (PostalAddress) firstAttributeValue.getValue(); + +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/JoseUtils.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/JoseUtils.java new file mode 100644 index 00000000..e81c4c92 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/JoseUtils.java @@ -0,0 +1,305 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.utils; + +import at.gv.egiz.eaaf.core.exception.EaafKeyUsageException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.utils.X509Utils; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.jose4j.jca.ProviderContext; +import org.jose4j.jwa.AlgorithmConstraints; +import org.jose4j.jws.AlgorithmIdentifiers; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.jwx.Headers; +import org.jose4j.jwx.JsonWebStructure; +import org.jose4j.keys.resolvers.X509VerificationKeyResolver; +import org.jose4j.lang.JoseException; +import org.springframework.util.Base64Utils; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.security.Key; +import java.security.KeyStore; +import java.security.Provider; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.RSAPrivateKey; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * {@link JoseUtils} provides static methods JWS and JWE processing. + * + * @author tlenz + * + */ +@Slf4j +public class JoseUtils { + +  /** +   * Create a JWS signature. +   * +   * <p> +   * Use {@link AlgorithmIdentifiers.RSA_PSS_USING_SHA256} in case +   * of a RSA based key and +   * {@link AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256} +   * in case of an ECC based key. +   * </p> +   * +   * @param keyStore               KeyStore that should be used +   * @param keyAlias               Alias of the private key +   * @param keyPassword            Password to access the key +   * @param payLoad                PayLoad to sign +   * @param addFullCertChain       If true the full certificate chain will be +   *                               added, otherwise only the +   *                               X509CertSha256Fingerprint is added into JOSE +   *                               header +   * @param friendlyNameForLogging FriendlyName for the used KeyStore for logging +   *                               purposes only +   * @return Signed PayLoad in serialized form +   * @throws EaafException In case of a key-access or key-usage error +   * @throws JoseException In case of a JOSE error +   */ +  public static String createSignature(@Nonnull Pair<KeyStore, Provider> keyStore, +      @Nonnull final String keyAlias, @Nonnull final char[] keyPassword, +      @Nonnull final String payLoad, boolean addFullCertChain, +      @Nonnull String friendlyNameForLogging) throws EaafException, JoseException { +    return createSignature(keyStore, keyAlias, keyPassword, payLoad, addFullCertChain, Collections.emptyMap(), +        AlgorithmIdentifiers.RSA_PSS_USING_SHA256, AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256, +        friendlyNameForLogging); + +  } + +  /** +   * Create a JWS signature. +   * +   * <p> +   * Use {@link AlgorithmIdentifiers.RSA_PSS_USING_SHA256} in case +   * of a RSA based key and +   * {@link AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256} +   * in case of an ECC based key. +   * </p> +   * +   * @param keyStore               KeyStore that should be used +   * @param keyAlias               Alias of the private key +   * @param keyPassword            Password to access the key +   * @param payLoad                PayLoad to sign +   * @param addFullCertChain       If true the full certificate chain will be +   *                               added, otherwise only the +   *                               X509CertSha256Fingerprint is added into JOSE +   *                               header +   * @param joseHeaders            HeaderName and HeaderValue that should be set +   *                               into JOSE header +   * @param friendlyNameForLogging FriendlyName for the used KeyStore for logging +   *                               purposes only +   * @return Signed PayLoad in serialized form +   * @throws EaafException In case of a key-access or key-usage error +   * @throws JoseException In case of a JOSE error +   */ +  public static String createSignature(@Nonnull Pair<KeyStore, Provider> keyStore, +      @Nonnull final String keyAlias, @Nonnull final char[] keyPassword, +      @Nonnull final String payLoad, boolean addFullCertChain, +      @Nonnull final Map<String, String> joseHeaders, +      @Nonnull String friendlyNameForLogging) throws EaafException, JoseException { +    return createSignature(keyStore, keyAlias, keyPassword, payLoad, addFullCertChain, joseHeaders, +        AlgorithmIdentifiers.RSA_PSS_USING_SHA256, AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256, +        friendlyNameForLogging); + +  } + +  /** +   * Create a JWS signature. +   * +   * @param keyStore               KeyStore that should be used +   * @param keyAlias               Alias of the private key +   * @param keyPassword            Password to access the key +   * @param payLoad                PayLoad to sign +   * @param addFullCertChain       If true the full certificate chain will be +   *                               added, otherwise only the +   *                               X509CertSha256Fingerprint is added into JOSE +   *                               header +   * @param joseHeaders            HeaderName and HeaderValue that should be set +   *                               into JOSE header +   * @param rsaAlgToUse            Signing algorithm that should be used in case +   *                               of a signing key based on RSA +   * @param eccAlgToUse            Signing algorithm that should be used in case +   *                               of a signing key based on ECC +   * @param friendlyNameForLogging FriendlyName for the used KeyStore for logging +   *                               purposes only +   * @return Signed PayLoad in serialized form +   * @throws EaafException In case of a key-access or key-usage error +   * @throws JoseException In case of a JOSE error +   */ +  public static String createSignature(@Nonnull Pair<KeyStore, Provider> keyStore, +      @Nonnull final String keyAlias, @Nonnull final char[] keyPassword, +      @Nonnull final String payLoad, boolean addFullCertChain, +      @Nonnull final Map<String, String> joseHeaders, +      @Nonnull final String rsaAlgToUse, @Nonnull final String eccAlgToUse, +      @Nonnull String friendlyNameForLogging) throws EaafException, JoseException { + +    final JsonWebSignature jws = new JsonWebSignature(); + +    // set payload +    jws.setPayload(payLoad); + +    // set JOSE headers +    for (final Entry<String, String> el : joseHeaders.entrySet()) { +      log.trace("Set JOSE header: {} with value: {} into JWS", el.getKey(), el.getValue()); +      jws.setHeader(el.getKey(), el.getValue()); + +    } + +    // set signing information +    final Pair<Key, X509Certificate[]> signingCred = EaafKeyStoreUtils.getPrivateKeyAndCertificates( +        keyStore.getFirst(), keyAlias, keyPassword, true, friendlyNameForLogging); +    jws.setKey(signingCred.getFirst()); +    jws.setAlgorithmHeaderValue(getKeyOperationAlgorithmFromCredential( +        jws.getKey(), rsaAlgToUse, eccAlgToUse, friendlyNameForLogging)); + +    // set special provider if required +    if (keyStore.getSecond() != null) { +      log.trace("Injecting special Java Security Provider: {}", keyStore.getSecond().getName()); +      final ProviderContext providerCtx = new ProviderContext(); +      providerCtx.getSuppliedKeyProviderContext().setSignatureProvider( +          keyStore.getSecond().getName()); +      jws.setProviderContext(providerCtx); + +    } + +    if (addFullCertChain) { +      jws.setCertificateChainHeaderValue(signingCred.getSecond()); + +    } + +    jws.setX509CertSha256ThumbprintHeaderValue(signingCred.getSecond()[0]); + +    return jws.getCompactSerialization(); + +  } + +  /** +   * Verify a JOSE signature. +   * +   * @param serializedContent Serialized content that should be verified +   * @param trustedCerts      Trusted certificates that should be used for +   *                          verification +   * @param constraints       {@link AlgorithmConstraints} for verification +   * @return {@link JwsResult} object +   * @throws JoseException In case of a signature verification error +   * @throws IOException   In case of a general error +   */ +  public static JwsResult validateSignature(@Nonnull final String serializedContent, +      @Nonnull final List<X509Certificate> trustedCerts, @Nonnull final AlgorithmConstraints constraints) +      throws JoseException, IOException { +    final JsonWebSignature jws = new JsonWebSignature(); +    // set payload +    jws.setCompactSerialization(serializedContent); + +    // set security constrains +    jws.setAlgorithmConstraints(constraints); + +    // load signinc certs +    Key selectedKey = null; +    final List<X509Certificate> x5cCerts = jws.getCertificateChainHeaderValue(); +    final String x5t256 = jws.getX509CertSha256ThumbprintHeaderValue(); +    if (x5cCerts != null) { +      log.debug("Found x509 certificate in JOSE header ... "); +      log.trace("Sorting received X509 certificates ... "); +      final List<X509Certificate> sortedX5cCerts = X509Utils.sortCertificates(x5cCerts); + +      if (trustedCerts.contains(sortedX5cCerts.get(0))) { +        selectedKey = sortedX5cCerts.get(0).getPublicKey(); + +      } else { +        log.info("Can NOT find JOSE certificate in truststore."); +        if (log.isDebugEnabled()) { +          try { +            log.debug("Cert: {}", Base64Utils.encodeToString(sortedX5cCerts.get(0).getEncoded())); + +          } catch (final CertificateEncodingException e) { +            log.warn("Can not create DEBUG output", e); + +          } +        } +      } + +    } else if (StringUtils.isNotEmpty(x5t256)) { +      log.debug("Found x5t256 fingerprint in JOSE header .... "); +      final X509VerificationKeyResolver x509VerificationKeyResolver = new X509VerificationKeyResolver( +          trustedCerts); +      selectedKey = x509VerificationKeyResolver.resolveKey(jws, Collections.<JsonWebStructure>emptyList()); + +    } else { +      throw new JoseException("JWS contains NO signature certificate or NO certificate fingerprint"); + +    } + +    if (selectedKey == null) { +      throw new JoseException("Can NOT select verification key for JWS. Signature verification FAILED"); + +    } + +    // set verification key +    jws.setKey(selectedKey); + +    // load payLoad +    return new JwsResult( +        jws.verifySignature(), +        jws.getUnverifiedPayload(), +        jws.getHeaders(), +        x5cCerts); + +  } + +  /** +   * Select signature algorithm for a given credential. +   * +   * @param key                    {@link X509Credential} that will be used for +   *                               key operations +   * @param rsaSigAlgorithm        RSA based algorithm that should be used in case +   *                               of RSA credential +   * @param ecSigAlgorithm         EC based algorithm that should be used in case +   *                               of RSA credential +   * @param friendlyNameForLogging KeyStore friendlyName for logging purposes +   * @return either the RSA based algorithm or the EC based algorithm +   * @throws EaafKeyUsageException In case of an unsupported private-key type +   */ +  private static String getKeyOperationAlgorithmFromCredential(Key key, +      String rsaSigAlgorithm, String ecSigAlgorithm, String friendlyNameForLogging) +      throws EaafKeyUsageException { +    if (key instanceof RSAPrivateKey) { +      return rsaSigAlgorithm; + +    } else if (key instanceof ECPrivateKey) { +      return ecSigAlgorithm; + +    } else { +      log.warn("Could NOT select the cryptographic algorithm from Private-Key type"); +      throw new EaafKeyUsageException(EaafKeyUsageException.ERROR_CODE_01, +          friendlyNameForLogging, +          "Can not select cryptographic algorithm"); + +    } + +  } + +  private JoseUtils() { + +  } + +  @Getter +  @AllArgsConstructor +  public static class JwsResult { +    final boolean valid; +    final String payLoad; +    final Headers fullJoseHeader; +    final List<X509Certificate> x5cCerts; + +  } +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/LoggingHandler.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/LoggingHandler.java new file mode 100644 index 00000000..70290cd3 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/LoggingHandler.java @@ -0,0 +1,72 @@ +/* + * 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.utils; + +import java.io.ByteArrayOutputStream; +import java.util.Set; + +import javax.xml.namespace.QName; +import javax.xml.soap.SOAPMessage; +import javax.xml.ws.handler.MessageContext; +import javax.xml.ws.handler.soap.SOAPHandler; +import javax.xml.ws.handler.soap.SOAPMessageContext; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LoggingHandler implements SOAPHandler<SOAPMessageContext> { + +  Logger log = LoggerFactory.getLogger(LoggingHandler.class); + +  @Override +  public boolean handleMessage(SOAPMessageContext context) { +    final SOAPMessage msg = context.getMessage(); +    final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + +    try { +      msg.writeTo(bos); +      log.trace(bos.toString("UTF-8")); +      log.trace(new String(bos.toByteArray(), "UTF-8")); + +    } catch (final Exception e) { +      log.trace(e.getMessage(), e); +    } +    return true; +  } + +  @Override +  public boolean handleFault(SOAPMessageContext context) { +    return handleMessage(context); +  } + +  @Override +  public void close(MessageContext context) { +  } + +  @Override +  public Set<QName> getHeaders() { +    return null; +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/validator/EidasResponseValidator.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/validator/EidasResponseValidator.java new file mode 100644 index 00000000..9d9a0647 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/validator/EidasResponseValidator.java @@ -0,0 +1,175 @@ +/* + * 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.validator; + +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableSet; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasValidationException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.EidasAttributeRegistry; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.EidasResponseUtils; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.impl.data.Triple; +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.LevelOfAssurance; + +/** + * eIDAS Response validator implementation. + *  + * @author tlenz + * + */ +public class EidasResponseValidator { +  private static final Logger log = LoggerFactory.getLogger(EidasResponseValidator.class); + +  /** +   * Validate an eIDAS Response according to internal state. +   *  +   * @param pendingReq Current pending request +   * @param eidasResponse eIDAS response object +   * @param spCountry Country-Code of the Service Provider +   * @param citizenCountryCode Country-Code of the Citizen +   * @param attrRegistry eIDAS Attribute registry implementation +   * @throws EidasValidationException In case of an validation error +   */ +  public static void validateResponse(IRequest pendingReq, ILightResponse eidasResponse, String spCountry, +      String citizenCountryCode, EidasAttributeRegistry attrRegistry) throws EidasValidationException { + +    /*-----------------------------------------------------| +     * validate received LoA against minimum required LoA  | +     *_____________________________________________________| +     */ +    final LevelOfAssurance respLoA = LevelOfAssurance.fromString(eidasResponse.getLevelOfAssurance()); +    final List<String> allowedLoAs = pendingReq.getServiceProviderConfiguration().getRequiredLoA(); +    boolean loaValid = false; +    for (final String allowedLoaString : allowedLoAs) { +      final LevelOfAssurance allowedLoa = LevelOfAssurance.fromString(allowedLoaString); +      if (respLoA.numericValue() >= allowedLoa.numericValue()) { +        log.debug("Response contains valid LoA. Resume process ... "); +        loaValid = true; +        break; + +      } else { +        log.trace("Allowed LoA: " + allowedLoaString + " DOES NOT match response LoA: " + eidasResponse +            .getLevelOfAssurance()); +      } + +    } + +    if (!loaValid) { +      log.error("eIDAS Response LevelOfAssurance is lower than the required! " +          + "(Resp-LoA:{} Req-LoA:{} )", respLoA.getValue(), allowedLoAs.toArray()); +      throw new EidasValidationException("eidas.06", new Object[] { respLoA.getValue() }); + +    } + +    /*-----------------------------------------------------| +     *     validate 'PersonalIdentifier' attribute         | +     *_____________________________________________________| +     */ +    final AttributeDefinition<?> attrDefinition = attrRegistry.getCoreAttributeRegistry().getByFriendlyName( +        Constants.eIDAS_ATTR_PERSONALIDENTIFIER).first(); +    final ImmutableSet<? extends AttributeValue<?>> attributeValues = eidasResponse.getAttributes() +        .getAttributeMap().get(attrDefinition); +    final List<String> personalIdObj = EidasResponseUtils.translateStringListAttribute(attrDefinition, +        attributeValues); + +    // check if attribute exists +    if (personalIdObj == null || personalIdObj.isEmpty()) { +      log.warn("eIDAS Response include NO 'PersonalIdentifier' attriubte " +          + ".... That can be a BIG problem in further processing steps"); +      throw new EidasValidationException("eidas.05", new Object[] { "NO 'PersonalIdentifier' attriubte" }); + +    } else if (personalIdObj.size() > 1) { +      log.warn("eIDAS Response include MORE THAN ONE 'PersonalIdentifier' attriubtes " +          + ".... That can be a BIG problem in further processing steps"); +      throw new EidasValidationException("eidas.05", new Object[] { +          "MORE THAN ONE 'PersonalIdentifier' attriubtes" }); + +    } else { +      final String natPersId = personalIdObj.get(0); +      // validate attribute value format +      final Triple<String, String, String> split = +          EidasResponseUtils.parseEidasPersonalIdentifier(natPersId); +      if (split == null) { +        throw new EidasValidationException("eidas.07", +            new Object[] { +                Constants.eIDAS_ATTR_PERSONALIDENTIFIER, +                "Wrong identifier format" }); + +      } else { +        // validation according to eIDAS SAML Attribute Profile, Section 2.2.3 +        if (StringUtils.isEmpty(split.getSecond())) { +          log.warn("eIDAS attribute value for " + Constants.eIDAS_ATTR_PERSONALIDENTIFIER +              + " includes NO destination country. Value:" + natPersId); +          throw new EidasValidationException("eidas.07", +              new Object[] { +                  Constants.eIDAS_ATTR_PERSONALIDENTIFIER, +                  "No or empty destination country" }); + +        } +        if (!split.getSecond().equalsIgnoreCase(spCountry)) { +          log.warn("eIDAS attribute value for " + Constants.eIDAS_ATTR_PERSONALIDENTIFIER +              + " includes wrong destination country. Value:" + natPersId +              + " SP-Country:" + spCountry); +          throw new EidasValidationException("eidas.07", +              new Object[] { +                  Constants.eIDAS_ATTR_PERSONALIDENTIFIER, +                  "Destination country does not match to SP country" }); + +        } + +        if (StringUtils.isEmpty(split.getFirst())) { +          log.warn("eIDAS attribute value for " + Constants.eIDAS_ATTR_PERSONALIDENTIFIER +              + " includes NO citizen country. Value:" + natPersId); +          throw new EidasValidationException("eidas.07", +              new Object[] { +                  Constants.eIDAS_ATTR_PERSONALIDENTIFIER, +                  "No or empty citizen country" }); + +        } +        if (!split.getFirst().equalsIgnoreCase(citizenCountryCode)) { +          log.warn("eIDAS attribute value for " + Constants.eIDAS_ATTR_PERSONALIDENTIFIER +              + " includes a citizen country that does not match to service-provider country. " +              + " Value:" + natPersId +              + " citiczen Country:" + spCountry); +          throw new EidasValidationException("eidas.07", +              new Object[] { +                  Constants.eIDAS_ATTR_PERSONALIDENTIFIER, +                  "Citizen country does not match to eIDAS-node country that generates the response" }); + +        } +      } +    } + +  } +} | 
