/* * 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.eIDAScentralAuth.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.apache.commons.lang3.StringUtils; 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.egiz.eaaf.core.api.idp.process.ExecutionContext; import at.gv.egiz.eaaf.core.exceptions.EAAFStorageException; import at.gv.egiz.eaaf.core.exceptions.InvalidProtocolRequestException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; 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.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.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 at.gv.egovernment.moa.id.advancedlogging.MOAIDEventConstants; import at.gv.egovernment.moa.id.auth.data.AuthenticationSessionWrapper; import at.gv.egovernment.moa.id.auth.exception.BuildException; import at.gv.egovernment.moa.id.auth.modules.eIDAScentralAuth.EidasCentralAuthConstants; import at.gv.egovernment.moa.id.auth.modules.eIDAScentralAuth.utils.EidasCentralAuthCredentialProvider; import at.gv.egovernment.moa.id.auth.modules.eIDAScentralAuth.utils.EidasCentralAuthMetadataProvider; import at.gv.egovernment.moa.id.auth.modules.eIDAScentralAuth.utils.Utils; import at.gv.egovernment.moa.id.commons.api.IOAAuthParameters; import at.gv.egovernment.moa.id.commons.api.exceptions.ConfigurationException; import at.gv.egovernment.moa.id.protocols.pvp2x.verification.SAMLVerificationEngineSP; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.MiscUtil; /** * @author tlenz * */ @Component("ReceiveFederatedAuthnResponseTask") public class ReceiveAuthnResponseTask extends AbstractAuthServletTask { @Autowired private SAMLVerificationEngineSP samlVerificationEngine; @Autowired private EidasCentralAuthCredentialProvider credentialProvider; @Autowired(required=true) EidasCentralAuthMetadataProvider 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; EAAFURICompare comperator = null; //select Response Binding if (request.getMethod().equalsIgnoreCase("POST")) { decoder = new PostBinding(); comperator = new EAAFURICompare(pendingReq.getAuthURL() + EidasCentralAuthConstants.ENDPOINT_POST); Logger.trace("Receive PVP Response from 'ms-specific eIDAS node', by using POST-Binding."); } else if (request.getMethod().equalsIgnoreCase("GET")) { decoder = new RedirectBinding(); comperator = new EAAFURICompare(pendingReq.getAuthURL() + EidasCentralAuthConstants.ENDPOINT_REDIRECT); Logger.trace("Receive PVP Response from 'ms-specific eIDAS node', 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[] {EidasCentralAuthConstants.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[] {EidasCentralAuthConstants.MODULE_NAME_FOR_LOGGING}); } //validate response signature if(!msg.isVerified()) { samlVerificationEngine.verify(msg, TrustEngineFactory.getSignatureKnownKeysTrustEngine(metadataProvider)); msg.setVerified(true); } //validate assertion PVPSProfileResponse processedMsg = preProcessAuthResponse((PVPSProfileResponse) msg); //validate entityId of response String msNodeEntityID = Utils.getCentraleIDASNodeEntityId(pendingReq.getServiceProviderConfiguration(), authConfig); String respEntityId = msg.getEntityID(); if (!msNodeEntityID.equals(respEntityId)) { Logger.warn("Response Issuer is not a 'ms-specific eIDAS node'. Stopping eIDAS authentication ..."); throw new AuthnResponseValidationException("sp.pvp2.08", new Object[] {EidasCentralAuthConstants.MODULE_NAME_FOR_LOGGING, msg.getEntityID()}); } //initialize Attribute extractor AssertionAttributeExtractor extractor = new AssertionAttributeExtractor((Response) processedMsg.getResponse()); getAuthDataFromInterfederation(extractor, pendingReq.getServiceProviderConfiguration(IOAAuthParameters.class)); //store pending-request requestStoreage.storePendingRequest(pendingReq); //write log entries revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROCESS_EIDAS_AT_CONNECTOR_MDS_VALID); Logger.info("Receive a valid assertion from IDP " + msg.getEntityID()); } catch (MessageDecodingException | SecurityException e) { String samlRequest = request.getParameter("SAMLRequest"); Logger.warn("Receive INVALID PVP Response from 'ms-specific eIDAS node': " + samlRequest, e); throw new TaskExecutionException(pendingReq, "Receive INVALID PVP Response from federated IDP", new AuthnResponseValidationException("sp.pvp2.11", new Object[] {"'national central eIDASNode'"}, e)); } catch (IOException | MarshallingException | TransformerException e) { Logger.warn("Processing PVP response from 'ms-specific eIDAS node' FAILED.", e); throw new TaskExecutionException(pendingReq, "Processing PVP response from 'ms-specific eIDAS node' FAILED.", new AuthnResponseValidationException("sp.pvp2.12", new Object[] {"'national central eIDASNode'", e.getMessage()}, e)); } catch (CredentialsNotAvailableException e) { Logger.error("PVP response decrytion FAILED. No credential found.", e); throw new TaskExecutionException(pendingReq, "PVP response decrytion FAILED. No credential found.", new AuthnResponseValidationException("sp.pvp2.10", new Object[] {"'national central eIDASNode'"}, e)); } catch (AssertionValidationExeption | AuthnResponseValidationException e) { Logger.info("PVP response validation FAILED. Msg:" + e.getMessage()); throw new TaskExecutionException(pendingReq, "PVP response validation FAILED.", new AuthnResponseValidationException("sp.pvp2.10", new Object[] {"'national central eIDASNode'"}, e)); } catch (Exception e) { Logger.warn("PVP response validation FAILED. Msg:" + e.getMessage(), e); throw new TaskExecutionException(pendingReq, "PVP response validation FAILED.", new AuthnResponseValidationException("sp.pvp2.12", new Object[] {"'national central eIDASNode'", e.getMessage()}, e)); } } private void getAuthDataFromInterfederation(AssertionAttributeExtractor extractor, IOAAuthParameters spConfig) throws BuildException, ConfigurationException{ try { //check if all attributes are include if (!extractor.containsAllRequiredAttributes() && !extractor.containsAllRequiredAttributes(EidasCentralAuthConstants.DEFAULT_REQUIRED_PVP_ATTRIBUTE_NAMES)) { Logger.warn("PVP Response from 'ms-specific eIDAS node' contains not all requested attributes."); throw new AssertionValidationExeption("sp.pvp2.06", new Object[]{EidasCentralAuthConstants.MODULE_NAME_FOR_LOGGING}); } //copy attributes into MOASession AuthenticationSessionWrapper session = pendingReq.getSessionData(AuthenticationSessionWrapper.class); Set includedAttrNames = extractor.getAllIncludeAttributeNames(); for (String el : includedAttrNames) { String value = extractor.getSingleAttributeValue(el); session.setGenericDataToSession(el, value); Logger.debug("Add PVP-attribute " + el + " into MOASession"); } //set foreigner flag session.setForeigner(true); if (extractor.getFullAssertion().getIssuer() != null && StringUtils.isNotEmpty(extractor.getFullAssertion().getIssuer().getValue())) session.setBkuURL(extractor.getFullAssertion().getIssuer().getValue()); else session.setBkuURL("eIDAS_Authentication"); } catch (AssertionValidationExeption e) { throw new BuildException("builder.06", null, e); } catch (EAAFStorageException e) { throw new BuildException("builder.06", null, e); } } /** * PreProcess AuthResponse and Assertion * @param msg * @throws TransformerException * @throws MarshallingException * @throws IOException * @throws CredentialsNotAvailableException * @throws AssertionValidationExeption * @throws AuthnResponseValidationException */ private PVPSProfileResponse preProcessAuthResponse(PVPSProfileResponse msg) throws IOException, MarshallingException, TransformerException, AssertionValidationExeption, CredentialsNotAvailableException, AuthnResponseValidationException { Logger.debug("Start PVP21 assertion processing... "); Response samlResp = (Response) msg.getResponse(); // 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() + EidasCentralAuthConstants.ENDPOINT_METADATA, EidasCentralAuthConstants.MODULE_NAME_FOR_LOGGING); msg.setSAMLMessage(SAML2Utils.asDOMDocument(samlResp).getDocumentElement()); revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROCESS_EIDAS_AT_CONNECTOR_RECEIVED, samlResp.getID()); return msg; } else { Logger.info("Receive StatusCode " + samlResp.getStatus().getStatusCode().getValue() + " from 'ms-specific eIDAS node'."); revisionsLogger.logEvent(pendingReq, MOAIDEventConstants.AUTHPROCESS_EIDAS_AT_CONNECTOR_RECEIVED_ERROR); throw new AuthnResponseValidationException("sp.pvp2.05", new Object[]{EidasCentralAuthConstants.MODULE_NAME_FOR_LOGGING, samlResp.getIssuer().getValue(), samlResp.getStatus().getStatusCode().getValue(), samlResp.getStatus().getStatusMessage().getMessage()}); } } }