/******************************************************************************* * 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.servlet; import iaik.x509.X509Certificate; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.util.ArrayList; import javax.activation.DataSource; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.transform.stream.StreamSource; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringEscapeUtils; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.opensaml.saml2.core.StatusCode; import at.gv.egovernment.moa.id.auth.AuthenticationServer; import at.gv.egovernment.moa.id.auth.builder.DataURLBuilder; import at.gv.egovernment.moa.id.auth.data.AuthenticationSession; import at.gv.egovernment.moa.id.auth.data.IdentityLink; import at.gv.egovernment.moa.id.auth.exception.AuthenticationException; import at.gv.egovernment.moa.id.auth.exception.MOAIDException; import at.gv.egovernment.moa.id.auth.exception.WrongParametersException; import at.gv.egovernment.moa.id.auth.stork.STORKException; import at.gv.egovernment.moa.id.auth.stork.STORKResponseProcessor; import at.gv.egovernment.moa.id.commons.db.ConfigurationDBUtils; import at.gv.egovernment.moa.id.commons.db.ex.MOADatabaseException; import at.gv.egovernment.moa.id.config.auth.AuthConfigurationProvider; import at.gv.egovernment.moa.id.config.auth.OAAuthParameter; import at.gv.egovernment.moa.id.moduls.ModulUtils; import at.gv.egovernment.moa.id.protocols.pvp2x.PVPConstants; import at.gv.egovernment.moa.id.storage.AuthenticationSessionStoreage; import at.gv.egovernment.moa.id.util.HTTPUtils; import at.gv.egovernment.moa.id.util.ParamValidatorUtils; import at.gv.egovernment.moa.id.util.VelocityProvider; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.StringUtils; import at.gv.util.xsd.xmldsig.SignatureType; import at.gv.util.xsd.xmldsig.X509DataType; import eu.stork.oasisdss.api.ApiUtils; import eu.stork.oasisdss.api.LightweightSourceResolver; import eu.stork.oasisdss.api.exceptions.ApiUtilsException; import eu.stork.oasisdss.profile.SignResponse; import eu.stork.peps.auth.commons.IPersonalAttributeList; import eu.stork.peps.auth.commons.PEPSUtil; import eu.stork.peps.auth.commons.PersonalAttribute; import eu.stork.peps.auth.commons.STORKAuthnRequest; import eu.stork.peps.auth.commons.STORKAuthnResponse; import eu.stork.peps.auth.engine.STORKSAMLEngine; import eu.stork.peps.exceptions.STORKSAMLEngineException; /** * Endpoint for receiving STORK response messages */ public class PEPSConnectorServlet extends AuthServlet { private static final long serialVersionUID = 1L; public static final String PEPSCONNECTOR_SERVLET_URL_PATTERN = "/PEPSConnector"; /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { super.doGet(request, response); } /** * Handles the reception of a STORK response message * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String pendingRequestID = null; try { Logger.info("PEPSConnector Servlet invoked, expecting C-PEPS message."); Logger.debug("This ACS endpoint is: " + HTTPUtils.getBaseURL(request)); super.setNoCachingHeadersInHttpRespone(request, response); Logger.trace("No Caching headers set for HTTP response"); //check if https or only http super.checkIfHTTPisAllowed(request.getRequestURL().toString()); Logger.debug("Trying to find MOA Session-ID"); String moaSessionID = request.getParameter(PARAM_SESSIONID); // escape parameter strings moaSessionID= StringEscapeUtils.escapeHtml(moaSessionID); if (StringUtils.isEmpty(moaSessionID)) { //No authentication session has been started before Logger.error("MOA-SessionID was not found, no previous AuthnRequest had been started"); Logger.debug("PEPSConnectorURL was: " + request.getRequestURL()); throw new AuthenticationException("auth.02", new Object[] { moaSessionID }); } if (!ParamValidatorUtils.isValidSessionID(moaSessionID)) throw new WrongParametersException("VerifyAuthenticationBlock", PARAM_SESSIONID, "auth.12"); pendingRequestID = AuthenticationSessionStoreage.getPendingRequestID(moaSessionID); //load MOASession from database AuthenticationSession moaSession = AuthenticationServer.getSession(moaSessionID); //change MOASessionID moaSessionID = AuthenticationSessionStoreage.changeSessionID(moaSession); Logger.info("Found MOA sessionID: " + moaSessionID); Logger.debug("Beginning to extract SAMLResponse out of HTTP Request"); //extract STORK Response from HTTP Request //Decodes SAML Response byte[] decSamlToken; try { decSamlToken = PEPSUtil.decodeSAMLToken(request.getParameter("SAMLResponse")); } catch(NullPointerException e) { Logger.error("Unable to retrieve STORK Response", e); throw new MOAIDException("stork.04", null); } //Get SAMLEngine instance STORKSAMLEngine engine = STORKSAMLEngine.getInstance("outgoing"); STORKAuthnResponse authnResponse = null; try { //validate SAML Token Logger.debug("Starting validation of SAML response"); authnResponse = engine.validateSTORKAuthnResponse(decSamlToken, (String) request.getRemoteHost()); Logger.info("SAML response succesfully verified!"); }catch(STORKSAMLEngineException e){ Logger.error("Failed to verify STORK SAML Response", e); throw new MOAIDException("stork.05", null); } Logger.info("STORK SAML Response message succesfully extracted"); Logger.debug("STORK response: "); Logger.debug(authnResponse.toString()); String statusCodeValue = authnResponse.getStatusCode(); if (!statusCodeValue.equals(StatusCode.SUCCESS_URI)) { Logger.error("Received ErrorResponse from PEPS: " + statusCodeValue); throw new MOAIDException("stork.06", new Object[] { statusCodeValue }); } Logger.info("Got SAML response with authentication success message."); Logger.debug("MOA session is still valid"); STORKAuthnRequest storkAuthnRequest = moaSession.getStorkAuthnRequest(); if (storkAuthnRequest == null) { Logger.error("Could not find any preceeding STORK AuthnRequest to this MOA session: " + moaSessionID); throw new MOAIDException("stork.07", null); } Logger.debug("Found a preceeding STORK AuthnRequest to this MOA session: " + moaSessionID); ////////////// incorporate gender from parameters if not in stork response IPersonalAttributeList attributeList = authnResponse.getPersonalAttributeList(); // but first, check if we have a representation case if(STORKResponseProcessor.hasAttribute("mandateContent", attributeList) || STORKResponseProcessor.hasAttribute("representative", attributeList) || STORKResponseProcessor.hasAttribute("represented", attributeList)) { // in a representation case... moaSession.setUseMandate("true"); // and check if we have the gender value PersonalAttribute gender = attributeList.get("gender"); if(null == gender) { String gendervalue = (String) request.getParameter("gender"); if(null != gendervalue) { gender = new PersonalAttribute(); gender.setName("gender"); ArrayList tmp = new ArrayList(); tmp.add(gendervalue); gender.setValue(tmp); authnResponse.getPersonalAttributeList().add(gender); } } } ////////////////////////////////////////////////////////////////////////// Logger.debug("Starting extraction of signedDoc attribute"); //extract signed doc element and citizen signature String citizenSignature = null; try { String signatureInfo = authnResponse.getPersonalAttributeList().get("signedDoc").getValue().get(0); SignResponse dssSignResponse = (SignResponse) ApiUtils.unmarshal(new StreamSource(new java.io.StringReader(signatureInfo))); // fetch signed doc DataSource ds = LightweightSourceResolver.getDataSource(dssSignResponse); if(ds == null){ throw new ApiUtilsException("No datasource found in response"); } InputStream incoming = ds.getInputStream(); citizenSignature = IOUtils.toString(incoming); incoming.close(); JAXBContext ctx = JAXBContext.newInstance(SignatureType.class.getPackage().getName()); SignatureType root = ((JAXBElement) ctx.createUnmarshaller().unmarshal(IOUtils.toInputStream(citizenSignature))).getValue(); // memorize signature into authblock moaSession.setAuthBlock(citizenSignature); // extract certificate for(Object current : root.getKeyInfo().getContent()) if(((JAXBElement) current).getValue() instanceof X509DataType) { for(Object currentX509Data : ((JAXBElement) current).getValue().getX509IssuerSerialOrX509SKIOrX509SubjectName()) { JAXBElement casted = ((JAXBElement) currentX509Data); if(casted.getName().getLocalPart().equals("X509Certificate")) { moaSession.setSignerCertificate(new X509Certificate(((String)casted.getValue()).getBytes())); break; } } } } catch (Throwable e) { Logger.error("Could not extract citizen signature from C-PEPS", e); throw new MOAIDException("stork.09", null); } Logger.debug("Foregin Citizen signature successfully extracted from STORK Assertion (signedDoc)"); Logger.debug("Citizen signature will be verified by SZR Gateway!"); Logger.debug("fetching OAParameters from database"); //read configuration paramters of OA AuthenticationSession moasession; try { moasession = AuthenticationSessionStoreage.getSession(moaSessionID); } catch (MOADatabaseException e2) { Logger.error("could not retrieve moa session"); throw new AuthenticationException("auth.01", null); } OAAuthParameter oaParam = AuthConfigurationProvider.getInstance().getOnlineApplicationParameter(moasession.getPublicOAURLPrefix()); if (oaParam == null) throw new AuthenticationException("auth.00", new Object[] { moasession.getPublicOAURLPrefix() }); // retrieve target //TODO: check in case of SSO!!! String targetType = null; String targetValue = null; if(oaParam.getBusinessService()) { String id = oaParam.getIdentityLinkDomainIdentifier(); if (id.startsWith(AuthenticationSession.REGISTERANDORDNR_PREFIX_)) targetValue = id.substring(AuthenticationSession.REGISTERANDORDNR_PREFIX_.length()); else targetValue = moasession.getDomainIdentifier(); targetType = AuthenticationSession.REGISTERANDORDNR_PREFIX_; } else { targetType = AuthenticationSession.TARGET_PREFIX_; targetValue = oaParam.getTarget(); } Logger.debug("Starting connecting SZR Gateway"); //contact SZR Gateway IdentityLink identityLink = null; try { identityLink = STORKResponseProcessor.connectToSZRGateway(authnResponse.getPersonalAttributeList(), oaParam.getFriendlyName(), targetType, targetValue, oaParam.getMandateProfiles()); } catch (STORKException e) { // this is really nasty but we work against the system here. We are supposed to get the gender attribute from // stork. If we do not, we cannot register the person in the ERnP - we have to have the // gender for the represented person. So here comes the dirty hack. if(e.getCause() instanceof STORKException && e.getCause().getMessage().equals("gender not found in response")) { try { Logger.trace("Initialize VelocityEngine..."); VelocityEngine velocityEngine = VelocityProvider.getClassPathVelocityEngine(); Template template = velocityEngine.getTemplate("/resources/templates/fetchGender.html"); VelocityContext context = new VelocityContext(); context.put("SAMLResponse", request.getParameter("SAMLResponse")); context.put("action", request.getRequestURL()); StringWriter writer = new StringWriter(); template.merge(context, writer); response.getOutputStream().write(writer.toString().getBytes()); } catch (Exception e1) { Logger.error("Error sending gender retrival form.", e1); // httpSession.invalidate(); throw new MOAIDException("stork.10", null); } return; } Logger.error("Error connecting SZR Gateway", e); throw new MOAIDException("stork.10", null); } Logger.debug("SZR communication was successfull"); if (identityLink == null) { Logger.error("SZR Gateway did not return an identity link."); throw new MOAIDException("stork.10", null); } Logger.info("Received Identity Link from SZR Gateway"); moaSession.setIdentityLink(identityLink); Logger.debug("Adding addtional STORK attributes to MOA session"); moaSession.setStorkAttributes(authnResponse.getPersonalAttributeList()); Logger.debug("Add full STORK AuthnResponse to MOA session"); moaSession.setStorkAuthnResponse(request.getParameter("SAMLResponse")); //We don't have BKUURL, setting from null to "Not applicable" moaSession.setBkuURL("Not applicable (STORK Authentication)"); // free for single use moaSession.setAuthenticatedUsed(false); // stork did the authentication step moaSession.setAuthenticated(true); //TODO: found better solution, but QAA Level in response could be not supported yet try { moasession.setQAALevel(authnResponse.getAssertions().get(0). getAuthnStatements().get(0).getAuthnContext(). getAuthnContextClassRef().getAuthnContextClassRef()); } catch (Throwable e) { Logger.warn("STORK QAA-Level is not found in AuthnResponse. Set QAA Level to requested level"); moasession.setQAALevel(PVPConstants.STORK_QAA_PREFIX + oaParam.getQaaLevel()); } //session is implicit stored in changeSessionID!!!! String newMOASessionID = AuthenticationSessionStoreage.changeSessionID(moaSession); Logger.info("Changed MOASession " + moaSessionID + " to Session " + newMOASessionID); //redirect String redirectURL = null; redirectURL = new DataURLBuilder().buildDataURL(moaSession.getAuthURL(), ModulUtils.buildAuthURL(moaSession.getModul(), moaSession.getAction(), pendingRequestID), newMOASessionID); redirectURL = response.encodeRedirectURL(redirectURL); response.setContentType("text/html"); response.setStatus(302); response.addHeader("Location", redirectURL); Logger.info("REDIRECT TO: " + redirectURL); } catch (AuthenticationException e) { handleError(null, e, request, response, pendingRequestID); } catch (MOAIDException e) { handleError(null, e, request, response, pendingRequestID); } catch (Exception e) { Logger.error("PEPSConnector has an interal Error.", e); } finally { ConfigurationDBUtils.closeSession(); } } }