diff options
Diffstat (limited to 'eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveMobilePhoneSignatureResponseAndSearchInRegistersTask.java')
-rw-r--r-- | eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveMobilePhoneSignatureResponseAndSearchInRegistersTask.java | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveMobilePhoneSignatureResponseAndSearchInRegistersTask.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveMobilePhoneSignatureResponseAndSearchInRegistersTask.java index b598cb92..9e6aa7cc 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveMobilePhoneSignatureResponseAndSearchInRegistersTask.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveMobilePhoneSignatureResponseAndSearchInRegistersTask.java @@ -29,18 +29,59 @@ import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.RegisterResult; import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; import at.asitplus.eidas.specific.modules.auth.eidas.v2.ernp.IErnpClient; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.ManualFixNecessaryException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.AhAuthProcessDataWrapper; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.AuthHandlerConstants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.EidasAuthEventConstants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.IdAustriaClientAuthConstants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.IdAustriaClientAuthCredentialProvider; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.IdAustriaClientAuthMetadataProvider; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.MisException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.Utils; import at.asitplus.eidas.specific.modules.auth.eidas.v2.zmr.IZmrClient; +import at.gv.egiz.eaaf.core.api.data.ExtendedPvpAttributeDefinitions; +import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions; import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.exceptions.EaafBuilderException; +import at.gv.egiz.eaaf.core.exceptions.EaafStorageException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.data.Pair; import at.gv.egiz.eaaf.core.impl.idp.auth.data.AuthProcessDataWrapper; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; +import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl; +import at.gv.egiz.eaaf.modules.pvp2.api.binding.IDecoder; +import at.gv.egiz.eaaf.modules.pvp2.exception.CredentialsNotAvailableException; +import at.gv.egiz.eaaf.modules.pvp2.exception.SamlAssertionValidationExeption; +import at.gv.egiz.eaaf.modules.pvp2.exception.SamlSigningException; +import at.gv.egiz.eaaf.modules.pvp2.impl.binding.PostBinding; +import at.gv.egiz.eaaf.modules.pvp2.impl.binding.RedirectBinding; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.InboundMessage; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.PvpSProfileResponse; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.EaafUriCompare; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.TrustEngineFactory; +import at.gv.egiz.eaaf.modules.pvp2.impl.verification.SamlVerificationEngine; +import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AssertionValidationExeption; +import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AuthnResponseValidationException; +import at.gv.egiz.eaaf.modules.pvp2.sp.impl.utils.AssertionAttributeExtractor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.opensaml.core.xml.io.MarshallingException; +import org.opensaml.messaging.decoder.MessageDecodingException; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.StatusCode; +import org.opensaml.saml.saml2.metadata.IDPSSODescriptor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import javax.naming.ConfigurationException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.xml.transform.TransformerException; +import java.io.IOException; +import java.util.Arrays; +import java.util.Base64; import java.util.List; +import java.util.Set; /** * Task that searches ErnB and ZMR before adding person to SZR. @@ -51,6 +92,31 @@ import java.util.List; @Component("ReceiveMobilePhoneSignatureResponseTask") public class ReceiveMobilePhoneSignatureResponseAndSearchInRegistersTask extends AbstractAuthServletTask { + @Autowired + private SamlVerificationEngine samlVerificationEngine; + @Autowired + private IdAustriaClientAuthCredentialProvider credentialProvider; + @Autowired(required = true) + IdAustriaClientAuthMetadataProvider metadataProvider; + + private static final String ERROR_PVP_03 = "sp.pvp2.03"; + private static final String ERROR_PVP_05 = "sp.pvp2.05"; + private static final String ERROR_PVP_06 = "sp.pvp2.06"; + private static final String ERROR_PVP_08 = "sp.pvp2.08"; + private static final String ERROR_PVP_10 = "sp.pvp2.10"; + private static final String ERROR_PVP_11 = "sp.pvp2.11"; + private static final String ERROR_PVP_12 = "sp.pvp2.12"; + + private static final String ERROR_MSG_00 = + "Receive INVALID PVP Response from federated IDP"; + private static final String ERROR_MSG_01 = + "Processing PVP response from 'ms-specific eIDAS node' FAILED."; + private static final String ERROR_MSG_02 = + "PVP response decrytion FAILED. No credential found."; + private static final String ERROR_MSG_03 = + "PVP response validation FAILED."; + + private final IErnpClient ernpClient; private final IZmrClient zmrClient; @@ -71,6 +137,123 @@ public class ReceiveMobilePhoneSignatureResponseAndSearchInRegistersTask extends SimpleEidasData eidData = authProcessData.getGenericDataFromSession(Constants.DATA_SIMPLE_EIDAS, SimpleEidasData.class); + + InboundMessage msg = null; + + try { + + IDecoder decoder = null; + EaafUriCompare comperator = null; + // select Response Binding + if (request.getMethod().equalsIgnoreCase("POST")) { + decoder = new PostBinding(); + comperator = new EaafUriCompare(pendingReq.getAuthUrl() + IdAustriaClientAuthConstants.ENDPOINT_POST); + log.trace("Receive PVP Response from 'ID Austria node', by using POST-Binding."); + + } else if (request.getMethod().equalsIgnoreCase("GET")) { + decoder = new RedirectBinding(); + comperator = new EaafUriCompare(pendingReq.getAuthUrl() + + IdAustriaClientAuthConstants.ENDPOINT_REDIRECT); + log.trace("Receive PVP Response from 'ID Austria node', by using Redirect-Binding."); + + } else { + log.warn("Receive PVP Response, but Binding (" + + request.getMethod() + ") is not supported."); + throw new AuthnResponseValidationException(ERROR_PVP_03, new Object[]{ + IdAustriaClientAuthConstants.MODULE_NAME_FOR_LOGGING}); + + } + + // decode PVP response object + msg = (InboundMessage) decoder.decode( + request, response, metadataProvider, IDPSSODescriptor.DEFAULT_ELEMENT_NAME, + comperator); + + // validate response signature + if (!msg.isVerified()) { + samlVerificationEngine.verify(msg, TrustEngineFactory.getSignatureKnownKeysTrustEngine( + metadataProvider)); + msg.setVerified(true); + + } + + // validate assertion + final Pair<PvpSProfileResponse, Boolean> processedMsg = + preProcessAuthResponse((PvpSProfileResponse) msg); + + //check if SAML2 response contains user-stop decision + if (processedMsg.getSecond()) { + stopProcessFromUserDecision(executionContext, request, response); + + } else { + // validate entityId of response + final String msNodeEntityID = authConfig.getBasicConfiguration( + IdAustriaClientAuthConstants.CONFIG_PROPS_NODE_ENTITYID); + final String respEntityId = msg.getEntityID(); + if (!msNodeEntityID.equals(respEntityId)) { + log.warn("Response Issuer is not a 'ms-specific eIDAS node'. Stopping eIDAS authentication ..."); + throw new AuthnResponseValidationException(ERROR_PVP_08, + new Object[]{IdAustriaClientAuthConstants.MODULE_NAME_FOR_LOGGING, + msg.getEntityID()}); + + } + + // initialize Attribute extractor + final AssertionAttributeExtractor extractor = + new AssertionAttributeExtractor(processedMsg.getFirst().getResponse()); + + getAuthDataFromInterfederation(extractor); + + // set NeedConsent to false, because user gives consont during authentication + pendingReq.setNeedUserConsent(false); + + // store pending-request + requestStoreage.storePendingRequest(pendingReq); + + //set E-ID process flag to execution context + final AhAuthProcessDataWrapper session = pendingReq.getSessionData( + AhAuthProcessDataWrapper.class); + executionContext.put(AuthHandlerConstants.PROCESSCONTEXT_WAS_EID_PROCESS, session.isEidProcess()); + executionContext.put(AuthHandlerConstants.HTTP_PARAM_USE_MANDATES, session.isMandateUsed()); + + + log.info("Receive a valid assertion from IDP " + msg.getEntityID()); + + } + + } catch (final AuthnResponseValidationException e) { + throw new TaskExecutionException(pendingReq, ERROR_MSG_03, e); + + } catch (MessageDecodingException | SecurityException | SamlSigningException e) { + //final String samlRequest = request.getParameter("SAMLRequest"); + //log.debug("Receive INVALID PVP Response from 'ms-specific eIDAS node': {}", + // samlRequest, null, e); + throw new TaskExecutionException(pendingReq, ERROR_MSG_00, + new AuthnResponseValidationException(ERROR_PVP_11, + new Object[]{IdAustriaClientAuthConstants.MODULE_NAME_FOR_LOGGING}, e)); + + } catch (IOException | MarshallingException | TransformerException e) { + log.debug("Processing PVP response from 'ms-specific eIDAS node' FAILED.", e); + throw new TaskExecutionException(pendingReq, ERROR_MSG_01, + new AuthnResponseValidationException(ERROR_PVP_12, + new Object[]{IdAustriaClientAuthConstants.MODULE_NAME_FOR_LOGGING, e.getMessage()}, + e)); + + } catch (final CredentialsNotAvailableException e) { + log.debug("PVP response decrytion FAILED. No credential found.", e); + throw new TaskExecutionException(pendingReq, ERROR_MSG_02, + new AuthnResponseValidationException(ERROR_PVP_10, + new Object[]{IdAustriaClientAuthConstants.MODULE_NAME_FOR_LOGGING}, e)); + + } catch (final Exception e) { + log.debug("PVP response validation FAILED. Msg:" + e.getMessage(), e); + throw new TaskExecutionException(pendingReq, ERROR_MSG_03, + new AuthnResponseValidationException(ERROR_PVP_12, + new Object[]{IdAustriaClientAuthConstants.MODULE_NAME_FOR_LOGGING, e.getMessage()}, e)); + + } + + //TODO extract bPK-ZP from response String bpkzp = "TODO"; MergedRegisterSearchResult result = searchInZmrAndErnp(bpkzp); @@ -93,6 +276,165 @@ public class ReceiveMobilePhoneSignatureResponseAndSearchInRegistersTask extends } } + private Pair<PvpSProfileResponse, Boolean> preProcessAuthResponse(PvpSProfileResponse msg) + throws IOException, MarshallingException, TransformerException, + CredentialsNotAvailableException, AuthnResponseValidationException, SamlAssertionValidationExeption { + log.debug("Start PVP21 assertion processing... "); + final Response samlResp = (Response) msg.getResponse(); + + // check SAML2 response status-code + if (samlResp.getStatus().getStatusCode().getValue().equals(StatusCode.SUCCESS)) { + // validate PVP 2.1 assertion + samlVerificationEngine.validateAssertion(samlResp, + credentialProvider.getMessageEncryptionCredential(), + pendingReq.getAuthUrl() + IdAustriaClientAuthConstants.ENDPOINT_METADATA, + IdAustriaClientAuthConstants.MODULE_NAME_FOR_LOGGING); + + msg.setSamlMessage(Saml2Utils.asDomDocument(samlResp).getDocumentElement()); + revisionsLogger.logEvent(pendingReq, + EidasAuthEventConstants.AUTHPROCESS_EIDAS_AT_CONNECTOR_RECEIVED, + samlResp.getID()); + return Pair.newInstance(msg, false); + + } else { + log.info("Receive StatusCode " + samlResp.getStatus().getStatusCode().getValue() + + " from 'ms-specific eIDAS node'."); + StatusCode subStatusCode = getSubStatusCode(samlResp); + if (subStatusCode != null + && IdAustriaClientAuthConstants.SAML2_STATUSCODE_USERSTOP.equals(subStatusCode.getValue())) { + log.info("Find 'User-Stop operation' in SAML2 response. Stopping authentication process ... "); + return Pair.newInstance(msg, true); + + } + + revisionsLogger.logEvent(pendingReq, + EidasAuthEventConstants.AUTHPROCESS_EIDAS_AT_CONNECTOR_RECEIVED_ERROR); + throw new AuthnResponseValidationException(ERROR_PVP_05, + new Object[]{IdAustriaClientAuthConstants.MODULE_NAME_FOR_LOGGING, + samlResp.getIssuer().getValue(), + samlResp.getStatus().getStatusCode().getValue(), + samlResp.getStatus().getStatusMessage().getMessage()}); + + } + + } + + /** + * Get SAML2 Sub-StatusCode if not <code>null</code>. + * + * @param samlResp SAML2 response + * @return Sub-StatusCode or <code>null</code> if it's not set + */ + private StatusCode getSubStatusCode(Response samlResp) { + if (samlResp.getStatus().getStatusCode().getStatusCode() != null + && StringUtils.isNotEmpty(samlResp.getStatus().getStatusCode().getStatusCode().getValue())) { + return samlResp.getStatus().getStatusCode().getStatusCode(); + } + return null; + } + + private void getAuthDataFromInterfederation(AssertionAttributeExtractor extractor) + throws EaafBuilderException, ConfigurationException { + + List<String> requiredEidasNodeAttributes = IdAustriaClientAuthConstants.DEFAULT_REQUIRED_PVP_ATTRIBUTE_NAMES; + if (authConfig.getBasicConfigurationBoolean( + AuthHandlerConstants.PROP_CONFIG_LEGACY_ALLOW, false)) { + log.trace("Build required attributes for legacy operaton ... "); + requiredEidasNodeAttributes = Arrays.asList( + PvpAttributeDefinitions.PVP_VERSION_NAME, + PvpAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_NAME, + PvpAttributeDefinitions.EID_ISSUING_NATION_NAME); + + } + + try { + // check if all attributes are include + if (!extractor.containsAllRequiredAttributes() + || !extractor.containsAllRequiredAttributes( + requiredEidasNodeAttributes)) { + log.warn("PVP Response from 'ms-specific eIDAS node' contains not all requested attributes."); + throw new AssertionValidationExeption(ERROR_PVP_06, new Object[]{ + IdAustriaClientAuthConstants.MODULE_NAME_FOR_LOGGING}); + + } + + // copy attributes into MOASession + final AhAuthProcessDataWrapper session = pendingReq.getSessionData( + AhAuthProcessDataWrapper.class); + final Set<String> includedAttrNames = extractor.getAllIncludeAttributeNames(); + for (final String attrName : includedAttrNames) { + injectAuthInfosIntoSession(session, attrName, + extractor.getSingleAttributeValue(attrName)); + + } + + //set piiTransactionId from eIDAS Connector + String piiTransactionId = extractor.getSingleAttributeValue( + ExtendedPvpAttributeDefinitions.EID_PII_TRANSACTION_ID_NAME); + if (StringUtils.isNotEmpty(piiTransactionId) && pendingReq instanceof RequestImpl) { + log.info("Receive piiTransactionId from Austrian eIDAS Connector. Use this for further processing"); + ((RequestImpl) pendingReq).setUniquePiiTransactionIdentifier(piiTransactionId); + + } else { + log.debug("Receive no piiTransactionId from Austrian eIDAS Connector."); + + } + + // set foreigner flag + session.setForeigner(true); + + // set IssuerInstant from Assertion + session.setIssueInstant(extractor.getAssertionIssuingDate()); + + // set CCE URL + if (extractor.getFullAssertion().getIssuer() != null + && StringUtils.isNotEmpty(extractor.getFullAssertion().getIssuer().getValue())) { + session.setVdaUrl(extractor.getFullAssertion().getIssuer().getValue()); + + } else { + session.setVdaUrl("eIDAS_Authentication"); + + } + + } catch (final EaafStorageException | MisException | AssertionValidationExeption | IOException e) { + throw new EaafBuilderException(ERROR_PVP_06, null, e.getMessage(), e); + + } + } + + private void injectAuthInfosIntoSession(AhAuthProcessDataWrapper session, String attrName, String attrValue) + throws EaafStorageException, MisException, IOException { + log.trace("Inject attribute: {} with value: {} into AuthSession", attrName, attrValue); + log.debug("Inject attribute: {} into AuthSession", attrName); + + if (ExtendedPvpAttributeDefinitions.EID_EIDBIND_NAME.equals(attrName)) { + log.debug("Find eidasBind attribute. Switching to E-ID mode ... "); + session.setEidProcess(true); + session.setQcBind(attrValue); + // session.setVsz(extractVszFromEidasBind(attrValue)); + //T + + } else if (ExtendedPvpAttributeDefinitions.EID_AUTHBLOCK_SIGNED_NAME.equals(attrName)) { + session.setSignedAuthBlock(Base64.getDecoder().decode(attrValue)); + session.setSignedAuthBlockType(AuthHandlerConstants.AuthBlockType.JWS); + + } else if (PvpAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_NAME.equals(attrName)) { + session.setQaaLevel(attrValue); + + // } else if (ExtendedPvpAttributeDefinitions.EID_MIS_MANDATE_NAME.equals(attrName) + // && authConfig.getBasicConfigurationBoolean( + // IdAustriaClientAuthConstants.CONFIG_PROPS_SEMPER_MANDATES_ACTIVE, false)) { + // session.setMandateDate(new SignedMandateDao(attrValue)); + // session.setUseMandates(true); + // + } else { + session.setGenericDataToSession(attrName, attrValue); + + } + + } + + private MergedRegisterSearchResult searchInZmrAndErnp(String bpkzp) { List<RegisterResult> resultsZmr = zmrClient.searchWithBpkZp(bpkzp); List<RegisterResult> resultsErnp = ernpClient.searchWithBpkZp(bpkzp); |