/* * Copyright 2014 Federal Chancellery Austria * MOA-ID has been developed in a cooperation between BRZ, the Federal * Chancellery Austria - ICT staff unit, and Graz University of Technology. * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * http://www.osor.eu/eupl/ * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. */ package at.gv.egovernment.moa.id.auth.modules.elgamandates.tasks; import java.io.IOException; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.transform.TransformerException; import org.opensaml.saml2.core.Response; import org.opensaml.saml2.core.StatusCode; import org.opensaml.ws.message.decoder.MessageDecodingException; import org.opensaml.xml.io.MarshallingException; import org.opensaml.xml.security.SecurityException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import at.gv.egovernment.moa.id.advancedlogging.MOAIDEventConstants; import at.gv.egovernment.moa.id.advancedlogging.MOAReversionLogger; import at.gv.egovernment.moa.id.auth.exception.InvalidProtocolRequestException; import at.gv.egovernment.moa.id.auth.modules.AbstractAuthServletTask; import at.gv.egovernment.moa.id.auth.modules.TaskExecutionException; import at.gv.egovernment.moa.id.auth.modules.elgamandates.ELGAMandatesAuthConstants; import at.gv.egovernment.moa.id.auth.modules.elgamandates.utils.ELGAMandateServiceMetadataProvider; import at.gv.egovernment.moa.id.auth.modules.elgamandates.utils.ELGAMandatesCredentialProvider; import at.gv.egovernment.moa.id.process.api.ExecutionContext; import at.gv.egovernment.moa.id.protocols.pvp2x.PVPConstants; import at.gv.egovernment.moa.id.protocols.pvp2x.PVPTargetConfiguration; import at.gv.egovernment.moa.id.protocols.pvp2x.binding.IDecoder; import at.gv.egovernment.moa.id.protocols.pvp2x.binding.MOAURICompare; import at.gv.egovernment.moa.id.protocols.pvp2x.binding.PostBinding; import at.gv.egovernment.moa.id.protocols.pvp2x.binding.RedirectBinding; import at.gv.egovernment.moa.id.protocols.pvp2x.exceptions.AssertionValidationExeption; import at.gv.egovernment.moa.id.protocols.pvp2x.exceptions.AuthnResponseValidationException; import at.gv.egovernment.moa.id.protocols.pvp2x.messages.InboundMessage; import at.gv.egovernment.moa.id.protocols.pvp2x.messages.MOAResponse; import at.gv.egovernment.moa.id.protocols.pvp2x.signer.CredentialsNotAvailableException; import at.gv.egovernment.moa.id.protocols.pvp2x.utils.AssertionAttributeExtractor; import at.gv.egovernment.moa.id.protocols.pvp2x.utils.SAML2Utils; import at.gv.egovernment.moa.id.protocols.pvp2x.verification.SAMLVerificationEngineSP; import at.gv.egovernment.moa.id.protocols.pvp2x.verification.TrustEngineFactory; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.MiscUtil; /** * @author tlenz * */ @Component("ReceiveElgaMandateResponseTask") public class ReceiveElgaMandateResponseTask extends AbstractAuthServletTask { @Autowired SAMLVerificationEngineSP samlVerificationEngine; @Autowired ELGAMandatesCredentialProvider credentialProvider; @Autowired ELGAMandateServiceMetadataProvider metadataProvider; /* (non-Javadoc) * @see at.gv.egovernment.moa.id.auth.modules.AbstractAuthServletTask#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 { InboundMessage msg = null; try { IDecoder decoder = null; MOAURICompare comperator = null; //select Response Binding if (request.getMethod().equalsIgnoreCase("POST")) { decoder = new PostBinding(); comperator = new MOAURICompare(pendingReq.getAuthURL() + ELGAMandatesAuthConstants.ENDPOINT_POST); Logger.debug("Receive PVP Response from ELGA mandate-service, by using POST-Binding."); } else if (request.getMethod().equalsIgnoreCase("GET")) { decoder = new RedirectBinding(); comperator = new MOAURICompare(pendingReq.getAuthURL() + ELGAMandatesAuthConstants.ENDPOINT_REDIRECT); Logger.debug("Receive PVP Response from ELGA mandate-service, by using Redirect-Binding."); } else { Logger.warn("Receive PVP Response, but Binding (" + request.getMethod() + ") is not supported."); throw new AuthnResponseValidationException("sp.pvp2.03", new Object[] {ELGAMandatesAuthConstants.MODULE_NAME_FOR_LOGGING}); } //decode PVP response object msg = (InboundMessage) decoder.decode(request, response, metadataProvider, true, comperator); if (MiscUtil.isEmpty(msg.getEntityID())) { throw new InvalidProtocolRequestException("sp.pvp2.04", new Object[] {ELGAMandatesAuthConstants.MODULE_NAME_FOR_LOGGING}); } //validate response signature if(!msg.isVerified()) { samlVerificationEngine.verify(msg, TrustEngineFactory.getSignatureKnownKeysTrustEngine(metadataProvider)); msg.setVerified(true); } Logger.debug("PVP Response from ELGA mandate-service is cryptographically valid."); revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROCESS_ELGA_MANDATE_RECEIVED_IP, request.getRemoteAddr()); //validate assertion MOAResponse processedMsg = preProcessAuthResponse((MOAResponse) msg); //write ELGA mandate information into MOASession AssertionAttributeExtractor extractor = new AssertionAttributeExtractor(processedMsg.getResponse()); //check if all attributes are include if (!extractor.containsAllRequiredAttributes( ELGAMandatesAuthConstants.getRequiredAttributeNames())) { Logger.warn("PVP Response from ELGA mandate-service contains not all requested attributes."); throw new AssertionValidationExeption("sp.pvp2.06", new Object[]{ELGAMandatesAuthConstants.MODULE_NAME_FOR_LOGGING}); } //load MOASession object defaultTaskInitialization(request, executionContext); /** * Mandate Reference-Value is generated from ELGA MandateServie --> * MOA-ID generated reference value is not equal to reference-value from ELGA MandateService * But MOA-ID refernece-value is also validated in 'inResponseTo' attribute from ELGA MandateService response */ //validate receive mandate reference-value // String responseRefValue = extractor.getSingleAttributeValue(PVPConstants.MANDATE_REFERENCE_VALUE_NAME); // if (!moasession.getMandateReferenceValue().equals(responseRefValue)) { // Logger.warn("PVP Response from ELGA mandate-service contains a not valid MandateReferenceValue."); // throw new AssertionValidationExeption("sp.pvp2.07", // new Object[]{ELGAMandatesAuthConstants.MODULE_NAME_FOR_LOGGING, // PVPConstants.MANDATE_REFERENCE_VALUE_FRIENDLY_NAME}); // // } Logger.debug("Validation of PVP Response from ELGA mandate-service is complete."); Set includedAttrNames = extractor.getAllIncludeAttributeNames(); for (String el : includedAttrNames) { moasession.setGenericDataToSession(el, extractor.getSingleAttributeValue(el)); Logger.debug("Add PVP-attribute " + el + " into MOASession"); } //store MOASession requestStoreage.storePendingRequest(pendingReq); //write revisions log entry revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROCESS_ELGA_MANDATE_RECEIVED, extractor.getSingleAttributeValue(PVPConstants.MANDATE_REFERENCE_VALUE_NAME)); //write mandate info's to revisions log revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.PERSONAL_INFORMATION_MANDATE_TYPE, extractor.getSingleAttributeValue(PVPConstants.MANDATE_TYPE_NAME)); revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.PERSONAL_INFORMATION_MANDATE_MANDATOR_TYPE, MOAReversionLogger.NAT_PERSON); revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.PERSONAL_INFORMATION_MANDATE_MANDATOR_HASH, revisionsLogger.buildPersonInformationHash( extractor.getSingleAttributeValue(PVPConstants.MANDATE_NAT_PER_GIVEN_NAME_NAME), extractor.getSingleAttributeValue(PVPConstants.MANDATE_NAT_PER_FAMILY_NAME_NAME), extractor.getSingleAttributeValue(PVPConstants.MANDATE_NAT_PER_BIRTHDATE_NAME))); Logger.info("Receive a valid assertion from ELGA mandate-service " + msg.getEntityID()); } catch (MessageDecodingException | SecurityException e) { String samlRequest = request.getParameter("SAMLRequest"); Logger.warn("Receive INVALID PVP Response from ELGA mandate-service: " + samlRequest, e); revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROCESS_ELGA_MANDATE_ERROR_RECEIVED); throw new TaskExecutionException(pendingReq, "Receive INVALID PVP Response from ELGA mandate-service", new AuthnResponseValidationException("sp.pvp2.12", new Object[]{ELGAMandatesAuthConstants.MODULE_NAME_FOR_LOGGING, e.getMessage()}, e)); } catch (IOException | MarshallingException | TransformerException e) { Logger.warn("Processing PVP response from ELGA mandate-service FAILED.", e); revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROCESS_ELGA_MANDATE_ERROR_RECEIVED); throw new TaskExecutionException(pendingReq, "Processing PVP response from ELGA mandate-service FAILED.", new AuthnResponseValidationException("sp.pvp2.12", new Object[]{ELGAMandatesAuthConstants.MODULE_NAME_FOR_LOGGING, e.getMessage()}, e)); } catch (CredentialsNotAvailableException e) { Logger.error("ELGA mandate-service: PVP response decrytion FAILED. No credential found.", e); revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROCESS_ELGA_MANDATE_ERROR_RECEIVED); throw new TaskExecutionException(pendingReq, "ELGA mandate-service: PVP response decrytion FAILED. No credential found.", e); } catch (AssertionValidationExeption | AuthnResponseValidationException e) { Logger.info("ELGA mandate-service: PVP response validation FAILED. Msg:" + e.getMessage()); revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROCESS_ELGA_MANDATE_ERROR_RECEIVED, e.getMessageId()); throw new TaskExecutionException(pendingReq, "ELGA mandate-service: PVP response validation FAILED.", e); } catch (Exception e) { Logger.info("ELGA mandate-service: General Exception. Msg:" + e.getMessage()); revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROCESS_ELGA_MANDATE_ERROR_RECEIVED); throw new TaskExecutionException(pendingReq, "ELGA mandate-service: General Exception.", e); } } /** * PreProcess AuthResponse and Assertion * @param msg * @throws TransformerException * @throws MarshallingException * @throws IOException * @throws CredentialsNotAvailableException * @throws AssertionValidationExeption * @throws AuthnResponseValidationException */ private MOAResponse preProcessAuthResponse(MOAResponse msg) throws IOException, MarshallingException, TransformerException, AssertionValidationExeption, CredentialsNotAvailableException, AuthnResponseValidationException { Logger.debug("Start PVP-2.1 assertion processing... "); Response samlResp = (Response) msg.getResponse(); //validate 'inResponseTo' attribute String authnReqID = pendingReq.getGenericData( PVPTargetConfiguration.DATAID_INTERFEDERATION_REQUESTID, String.class); String inResponseTo = samlResp.getInResponseTo(); if (MiscUtil.isEmpty(authnReqID) || MiscUtil.isEmpty(inResponseTo) || !authnReqID.equals(inResponseTo)) { Logger.info("Validation of request/response IDs FAILED." + " ReqID:" + authnReqID + " InRespTo:" + inResponseTo); throw new AuthnResponseValidationException("sp.pvp2.07", new Object[]{ELGAMandatesAuthConstants.MODULE_NAME_FOR_LOGGING, "'InResponseTo'"}); } // check SAML2 response status-code if (samlResp.getStatus().getStatusCode().getValue().equals(StatusCode.SUCCESS_URI)) { //validate PVP 2.1 assertion samlVerificationEngine.validateAssertion(samlResp, true, credentialProvider.getIDPAssertionEncryptionCredential(), pendingReq.getAuthURL() + ELGAMandatesAuthConstants.ENDPOINT_METADATA, ELGAMandatesAuthConstants.MODULE_NAME_FOR_LOGGING); msg.setSAMLMessage(SAML2Utils.asDOMDocument(samlResp).getDocumentElement()); return msg; } else { String errorMsg = "No error message"; StatusCode firstCode = samlResp.getStatus().getStatusCode(); //get errormessage from response if (samlResp.getStatus().getStatusMessage() != null && MiscUtil.isNotEmpty(samlResp.getStatus().getStatusMessage().getMessage())) errorMsg = samlResp.getStatus().getStatusMessage().getMessage(); //extract response status-codes if (firstCode.getStatusCode() == null) { Logger.info("Receive StatusCode:" + firstCode.getValue() + " | Msg:" + errorMsg + " from federated IDP."); throw new AuthnResponseValidationException("sp.pvp2.05", new Object[]{ELGAMandatesAuthConstants.MODULE_NAME_FOR_LOGGING, samlResp.getIssuer().getValue(), firstCode.getValue(), samlResp.getStatus().getStatusMessage().getMessage()}); } else { StatusCode secondCode = firstCode.getStatusCode(); Logger.info("Receive StatusCode:" + firstCode.getValue() + " -> StatusCode:" + secondCode.getValue() + " | Msg:" + errorMsg + " from federated IDP."); throw new AuthnResponseValidationException("sp.pvp2.09", new Object[]{ELGAMandatesAuthConstants.MODULE_NAME_FOR_LOGGING, samlResp.getIssuer().getValue(), firstCode.getValue(), secondCode.getValue(), samlResp.getStatus().getStatusMessage().getMessage()}); } } } }