/******************************************************************************* * 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.protocols.eidas; import java.io.StringWriter; import java.security.MessageDigest; import java.text.SimpleDateFormat; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.opensaml.saml2.core.StatusCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.google.common.collect.ImmutableSet; import at.gv.egovernment.moa.id.advancedlogging.MOAReversionLogger; import at.gv.egovernment.moa.id.auth.frontend.velocity.VelocityProvider; import at.gv.egovernment.moa.id.auth.modules.eidas.Constants; import at.gv.egovernment.moa.id.auth.modules.eidas.engine.MOAeIDASChainingMetadataProvider; import at.gv.egovernment.moa.id.auth.modules.eidas.utils.SimpleEidasAttributeGenerator; import at.gv.egovernment.moa.id.auth.modules.eidas.utils.eIDASAttributeProcessingUtils; import at.gv.egovernment.moa.id.commons.MOAIDConstants; import at.gv.egovernment.moa.id.commons.api.IRequest; import at.gv.egovernment.moa.id.commons.api.exceptions.MOAIDException; import at.gv.egovernment.moa.id.data.IAuthData; import at.gv.egovernment.moa.id.data.SLOInformationImpl; import at.gv.egovernment.moa.id.data.SLOInformationInterface; import at.gv.egovernment.moa.id.data.Trible; import at.gv.egovernment.moa.id.moduls.IAction; import at.gv.egovernment.moa.id.protocols.builder.attributes.IAttributeGenerator; import at.gv.egovernment.moa.id.protocols.builder.attributes.MandateLegalPersonFullNameAttributeBuilder; import at.gv.egovernment.moa.id.protocols.builder.attributes.MandateLegalPersonSourcePinAttributeBuilder; import at.gv.egovernment.moa.id.protocols.pvp2x.builder.attributes.exceptions.AttributeException; import at.gv.egovernment.moa.id.util.Random; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.Base64Utils; import at.gv.egovernment.moa.util.MiscUtil; import eu.eidas.auth.commons.EidasStringUtil; import eu.eidas.auth.commons.attribute.AttributeDefinition; import eu.eidas.auth.commons.attribute.AttributeDefinition.Builder; 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.ImmutableAttributeMap; import eu.eidas.auth.commons.protocol.IResponseMessage; import eu.eidas.auth.commons.protocol.impl.AuthenticationResponse; import eu.eidas.auth.commons.protocol.impl.SamlNameIdFormat; import eu.eidas.auth.engine.ProtocolEngineI; import eu.eidas.auth.engine.xml.opensaml.SAMLEngineUtils; /** * Second request step - after authentication of the user is done and moasession obtained, * process request and forward the user further to PEPS and/or other entities * * @author tlenz */ @Service("eIDASAuthenticationRequest") public class eIDASAuthenticationRequest implements IAction { private static IAttributeGenerator generator = new SimpleEidasAttributeGenerator(); @Autowired protected MOAReversionLogger revisionsLogger; @Autowired(required=true) MOAeIDASChainingMetadataProvider eIDASMetadataProvider; @Override public SLOInformationInterface processRequest(IRequest req, HttpServletRequest httpReq, HttpServletResponse httpResp, IAuthData authData) throws MOAIDException { EIDASData eidasRequest; if(req instanceof EIDASData) eidasRequest = (EIDASData) req; else throw new MOAIDException("got wrong IRequest type. is: {}, should be: {}", new String[] {req.getClass().toString(), EIDASData.class.toString()}); String subjectNameID = null; //gather attributes ImmutableAttributeMap reqAttributeList = (ImmutableAttributeMap) eidasRequest.getEidasRequestedAttributes(); ImmutableAttributeMap.Builder attrMapBuilder = ImmutableAttributeMap.builder(); //TODO: if we support more then this minimum required attributes -> redesign to a smoother attribute builder selector for(AttributeDefinition attr : reqAttributeList.getDefinitions()) { String newValue = ""; boolean isUniqueID = false; try { switch(attr.getFriendlyName()) { case Constants.eIDAS_ATTR_DATEOFBIRTH: newValue = new SimpleDateFormat("YYYY-MM-dd").format(authData.getDateOfBirth()); break; case Constants.eIDAS_ATTR_CURRENTFAMILYNAME: newValue = authData.getFamilyName(); break; case Constants.eIDAS_ATTR_CURRENTGIVENNAME: newValue = authData.getGivenName(); break; case Constants.eIDAS_ATTR_PERSONALIDENTIFIER: newValue = authData.getBPK(); isUniqueID = true; //generate eIDAS conform 'PersonalIdentifier' attribute if (!eIDASAttributeProcessingUtils.validateEidasPersonalIdentifier(newValue)) { Logger.debug("preCalculated PersonalIdentifier does not include eIDAS conform prefixes ... add prefix now"); if (MiscUtil.isEmpty(authData.getBPKType()) || !authData.getBPKType().startsWith(at.gv.egovernment.moa.util.Constants.URN_PREFIX_EIDAS)) { Logger.error("BPKType is empty or does not start with eIDAS bPKType prefix! bPKType:" + authData.getBPKType()); throw new MOAIDException("builder.08", new Object[]{"Suspect bPKType for eIDAS identifier generation"}); } String prefix = authData.getBPKType().substring(at.gv.egovernment.moa.util.Constants.URN_PREFIX_EIDAS.length() + 1); newValue = prefix.replaceAll("\\+", "/") + "/" + newValue; } //generate a transient unique identifier if it is requested String reqNameIDFormat = eidasRequest.getEidasRequest().getNameIdFormat(); if (MiscUtil.isNotEmpty(reqNameIDFormat) && reqNameIDFormat.equals(SamlNameIdFormat.TRANSIENT.getNameIdFormat())) newValue = generateTransientNameID(newValue); subjectNameID = newValue; break; case Constants.eIDAS_ATTR_LEGALPERSONIDENTIFIER: newValue = new MandateLegalPersonSourcePinAttributeBuilder().build( req.getOnlineApplicationConfiguration(), authData, generator); break; case Constants.eIDAS_ATTR_LEGALNAME: newValue = new MandateLegalPersonFullNameAttributeBuilder().build( req.getOnlineApplicationConfiguration(), authData, generator); break; } } catch (AttributeException e) { Logger.debug("Attribute can not generate requested attribute:" + attr.getFriendlyName() + " Reason:" + e.getMessage()); } if(MiscUtil.isEmpty(newValue)) { if (attr.isRequired()) { Logger.info("eIDAS Attr:" + attr.getNameUri() + " is marked as 'Required' but not available."); throw new MOAIDException("eIDAS.15", new Object[]{attr.getFriendlyName()}); } else Logger.info("eIDAS Attr:" + attr.getNameUri() + " is not available."); } else { //set uniqueIdentifier attribute, because eIDAS SAMLEngine use this flag to select the // Subject->NameID value from this attribute Builder attrBuilder = AttributeDefinition.builder(attr); attrBuilder.uniqueIdentifier(isUniqueID); AttributeDefinition returnAttr = attrBuilder.build(); //unmarshal attribute value into eIDAS attribute AttributeValueMarshaller attributeValueMarshaller = returnAttr.getAttributeValueMarshaller(); ImmutableSet.Builder> builder = ImmutableSet.builder(); AttributeValue attributeValue = null; try { attributeValue = attributeValueMarshaller.unmarshal(newValue, false); builder.add(attributeValue); } catch (AttributeValueMarshallingException e) { throw new IllegalStateException(e); } //add attribute to Map attrMapBuilder.put((AttributeDefinition)returnAttr, (ImmutableSet) builder.build()); } } // construct eIDaS response AuthenticationResponse.Builder responseBuilder = new AuthenticationResponse.Builder(); responseBuilder.id(SAMLEngineUtils.generateNCName()); responseBuilder.inResponseTo(eidasRequest.getEidasRequest().getId()); String pubURLPrefix = req.getAuthURL(); String metadata_url = pubURLPrefix + Constants.eIDAS_HTTP_ENDPOINT_METADATA; responseBuilder.issuer(metadata_url); responseBuilder.levelOfAssurance(authData.getEIDASQAALevel()); //add attributes responseBuilder.attributes(attrMapBuilder.build()); //set success statuscode responseBuilder.statusCode(StatusCode.SUCCESS_URI); //build response AuthenticationResponse response = responseBuilder.build(); String token = null; IResponseMessage eIDASRespMsg = null; try { ProtocolEngineI engine = at.gv.egovernment.moa.id.auth.modules.eidas.utils.SAMLEngineUtils.createSAMLEngine(eIDASMetadataProvider); // encryption is done by the SamlEngine, i.e. by the module we provide in the config // but we need to set the appropriate request issuer //engine.setRequestIssuer(eidasRequest.getEidasRequest().getIssuer()); eIDASRespMsg = engine.generateResponseMessage(eidasRequest.getEidasRequest(), response, true, eidasRequest.getRemoteAddress()); // if(null == eidasRequest.getEidasRequest().getAssertionConsumerServiceURL()) { // String assertionConsumerUrl = MetadataUtil.getAssertionUrlFromMetadata( // new MOAeIDASMetadataProviderDecorator(eIDASMetadataProvider), // engine, // eidasRequest.getEidasRequest()); // eidasRequest.getEidasRequest().setAssertionConsumerServiceURL(assertionConsumerUrl); // // } // response = engine.generateEIDASAuthnResponse(eidasRequest.getEidasRequest(), response, eidasRequest.getRemoteAddress(), true); token = EidasStringUtil.encodeToBase64(eIDASRespMsg.getMessageBytes()); } catch(Exception e) { Logger.error("eIDAS Response encoding error." , e); throw new MOAIDException("eIDAS.13", new Object[]{e.getMessage()}, e); } revisionsLogger.logEvent(req, Constants.eIDAS_REVERSIONSLOG_IDP_AUTHRESPONSE, eIDASRespMsg.getResponse().getId()); // send the response try { VelocityEngine velocityEngine = VelocityProvider.getClassPathVelocityEngine(); Template template = velocityEngine.getTemplate("/resources/templates/eidas_postbinding_template.vm"); VelocityContext context = new VelocityContext(); context.put("RelayState", eidasRequest.getRemoteRelayState()); context.put("SAMLResponse", token); Logger.debug("SAMLResponse original: " + token); Logger.debug("Putting assertion consumer url as action: " + eidasRequest.getEidasRequest().getAssertionConsumerServiceURL()); context.put("action", eidasRequest.getEidasRequest().getAssertionConsumerServiceURL()); Logger.trace("Starting template merge"); StringWriter writer = new StringWriter(); Logger.trace("Doing template merge"); template.merge(context, writer); Logger.trace("Template merge done"); Logger.trace("Sending html content : " + new String(writer.getBuffer())); byte[] content = writer.getBuffer().toString().getBytes("UTF-8"); httpResp.setContentType(MOAIDConstants.DEFAULT_CONTENT_TYPE_HTML_UTF8); httpResp.setContentLength(content.length); httpResp.getOutputStream().write(content); } catch (Exception e) { Logger.error("Velocity error: " + e.getMessage()); throw new MOAIDException("eIDAS.13", new Object[]{e.getMessage()}, e); } SLOInformationInterface ssoContainer = null; try { ssoContainer = new SLOInformationImpl( req.getAuthURL(), eidasRequest.getEidasRequest().getIssuer(), null, subjectNameID, eidasRequest.getEidasRequest().getNameIdFormat(), EIDASProtocol.NAME); } catch (Exception e) { Logger.error("Can not generate container with SSO information!", e); } return ssoContainer; } @Override public boolean needAuthentication(IRequest req, HttpServletRequest httpReq, HttpServletResponse httpResp) { return true; } @Override public String getDefaultActionName() { return "eIDAS_AuthnRequest"; } private String generateTransientNameID(String nameID) { //extract source-country and destination country from persistent identifier Trible split = eIDASAttributeProcessingUtils.parseEidasPersonalIdentifier(nameID); if (split == null) { Logger.error("eIDAS 'PersonalIdentifier' has a wrong format. There had to be a ERROR in implementation!!!!"); throw new IllegalStateException("eIDAS 'PersonalIdentifier' has a wrong format. There had to be a ERROR in implementation!!!!"); } //build correct formated transient identifier String random = Random.nextLongRandom(); try { MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] hash = md.digest((split.getThird() + random).getBytes("ISO-8859-1")); return split.getFirst() + "/" + split.getSecond() + "/" + Base64Utils.encode(hash); } catch (Exception e) { Logger.error("Can not generate transient personal identifier!", e); return null; } } }