Java tutorial
/* * 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/european-union-public-licence-eupl-v.1.1 * * 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. */ package eu.eidas.auth.engine; import eu.eidas.auth.commons.*; import eu.eidas.auth.engine.core.*; import eu.eidas.auth.engine.core.eidas.EidasExtensionProcessor; import eu.eidas.auth.engine.core.eidas.GenericEidasAttributeType; import eu.eidas.auth.engine.core.stork.QAAAttribute; import eu.eidas.auth.engine.core.stork.StorkExtensionProcessor; import eu.eidas.auth.engine.core.validator.STORKAttributes; import eu.eidas.auth.engine.core.validator.eidas.EIDASAttributes; import eu.eidas.configuration.SAMLBootstrap; import eu.eidas.engine.exceptions.SAMLEngineException; import eu.eidas.engine.exceptions.EIDASSAMLEngineException; import eu.eidas.samlengineconfig.CertificateConfigurationManager; import org.apache.commons.lang.StringUtils; import org.bouncycastle.asn1.x500.X500Name; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.opensaml.Configuration; import org.opensaml.common.SAMLVersion; import org.opensaml.common.SignableSAMLObject; import org.opensaml.common.xml.SAMLConstants; import org.opensaml.common.xml.SAMLSchemaBuilder; import org.opensaml.saml2.common.Extensions; import org.opensaml.saml2.core.*; import org.opensaml.xml.ConfigurationException; import org.opensaml.xml.Namespace; import org.opensaml.xml.XMLObject; import org.opensaml.xml.parse.BasicParserPool; import org.opensaml.xml.parse.XMLParserException; import org.opensaml.xml.schema.XSAny; import org.opensaml.xml.schema.impl.*; import org.opensaml.xml.security.BasicSecurityConfiguration; import org.opensaml.xml.signature.KeyInfo; import org.opensaml.xml.util.Base64; import org.opensaml.xml.validation.ValidationException; import org.opensaml.xml.validation.ValidatorSuite; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; import javax.xml.namespace.QName; import javax.xml.transform.*; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.*; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.*; import java.util.concurrent.atomic.AtomicLong; /** * Class that wraps the operations over SAML tokens, both generation and * validation of SAML EIDAS requests and SAML EIDAS responses. Complaint with * "OASIS Secure Assertion Markup Language (SAML) 2.0, May 2005", but taking * into account EIDAS (and other supported formats) specific requirements. * * @author fjquevedo * @author iinigo */ public final class EIDASSAMLEngine extends AbstractSAMLEngine { /** The Constant LOG. */ private static final Logger LOG = LoggerFactory.getLogger(EIDASSAMLEngine.class.getName()); private static final int HEXA = 16; private static final String EIDAS_NATURALPERSON_IDENTIFIER = "http://eidas.europa.eu/attributes/naturalperson/PersonIdentifier"; private static final String EIDAS_LEGALPERSON_IDENTIFIER = "http://eidas.europa.eu/attributes/legalperson/LegalPersonIdentifier"; private static final String VALIDATION_MESSAGE_SEPARATOR = ", "; private static final String UNIQUE_ID_POSTFIX = "/uniqueid"; private SAMLEngineClock clock; /** * Allows overriding system clock for testing purposes. * * @param clock the SAMLEngineClock to be used */ public void setClock(SAMLEngineClock clock) { this.clock = clock; } public static final String ATTRIBUTE_EMPTY_LITERAL = "Attribute name is null or empty."; /** * Creates an instance of EIDASSAMLEngine. * * @param nameInstance the name instance * @return instance of EIDASSAMLEngine */ public static synchronized EIDASSAMLEngine createSAMLEngine(final String nameInstance) throws EIDASSAMLEngineException { return createSAMLEngine(nameInstance, null); } public static synchronized EIDASSAMLEngine createSAMLEngine(final String nameInstance, CertificateConfigurationManager configManager) throws EIDASSAMLEngineException { EIDASSAMLEngine engine = null; LOG.info(SAML_EXCHANGE, "Get instance: {} ", nameInstance); try { engine = new EIDASSAMLEngine(nameInstance.trim(), configManager); } catch (EIDASSAMLEngineException e) { throw e; } catch (Exception e) { LOG.error("Error get instance: " + nameInstance + " {}", e); } return engine; } private static AtomicLong counter = new AtomicLong(0); private long id; private EIDASSAMLEngine(final String nameInstance, final CertificateConfigurationManager configManager) throws EIDASSAMLEngineException, ConfigurationException { this(nameInstance, DEFAULT_CONFIG_NAME, configManager); } /** * Instantiate a new EIDASSAML engine. * * @param nameInstance the name instance * * @throws EIDASSAMLEngineException the EIDASSAML engine exception */ private EIDASSAMLEngine(final String nameInstance, final String configName, final CertificateConfigurationManager configManager) throws EIDASSAMLEngineException, ConfigurationException { // Initialization OpenSAML. super(nameInstance, configName, configManager); id = counter.incrementAndGet(); LOG.trace("Register EIDAS objects provider."); Configuration.registerObjectProvider(XSAny.TYPE_NAME, new XSAnyBuilder(), new XSAnyMarshaller(), new XSAnyUnmarshaller()); SAMLBootstrap.bootstrap(); // Registering a new system clock this.setClock(new SAMLEngineSystemClock()); setDigestMethodAlgorithm(null); } @Override public int hashCode() { return (int) (id % 97); } @Override public boolean equals(Object obj) { if (obj instanceof EIDASSAMLEngine) { return id == ((EIDASSAMLEngine) obj).id; } return false; } public void setDigestMethodAlgorithm(String algorithm) { BasicSecurityConfiguration config = SAMLEngineUtils.getEidasGlobalSecurityConfiguration(); if (config != null && StringUtils.isNotBlank(algorithm)) { config.setSignatureReferenceDigestMethod(SAMLEngineUtils.validateDigestAlgorithm(algorithm)); } else { LOG.error("Configuration error - Unable to set DigestMethodAlgorithm - config {} algorithm {} not set", config, algorithm); } } /** * Generate authentication response base. * * @param status the status * @param assertConsumerURL the assert consumer URL. * @param inResponseTo the in response to * * @return the response * * @throws EIDASSAMLEngineException the EIDASSAML engine exception */ private Response genAuthnRespBase(final Status status, final String assertConsumerURL, final String inResponseTo) throws EIDASSAMLEngineException { LOG.debug("Generate Authentication Response base."); final Response response = SAMLEngineUtils.generateResponse(SAMLEngineUtils.generateNCName(), SAMLEngineUtils.getCurrentTime(), status); // Set name Spaces this.setResponseNameSpaces(response); // Mandatory EIDAS LOG.debug("Generate Issuer"); final Issuer issuer = SAMLEngineUtils.generateIssuer(); issuer.setValue(super.getSamlCoreProperties().getResponder()); // Format Entity Optional EIDAS issuer.setFormat(super.getSamlCoreProperties().getFormatEntity()); response.setIssuer(issuer); // destination Mandatory EIDAS if (assertConsumerURL != null) { response.setDestination(assertConsumerURL.trim()); } // inResponseTo Mandatory response.setInResponseTo(inResponseTo.trim()); // Optional response.setConsent(super.getSamlCoreProperties().getConsentAuthnResponse()); return response; } /** * Generate assertion. * * @param ipAddress the IP address. * @param request the request for which the response is prepared * @param notOnOrAfter the not on or after * * @return the assertion * * @throws EIDASSAMLEngineException the EIDASSAML engine exception */ private Assertion generateAssertion(final String ipAddress, final EIDASAuthnRequest request, Response response, IPersonalAttributeList pal, final DateTime notOnOrAfter) throws EIDASSAMLEngineException { LOG.trace("Generate Assertion."); // Mandatory LOG.trace("Generate Issuer to Assertion"); final Issuer issuerAssertion = SAMLEngineUtils.generateIssuer(); issuerAssertion.setValue(response.getIssuer().getValue()); // Format Entity Optional issuerAssertion.setFormat(super.getSamlCoreProperties().getFormatEntity()); final Assertion assertion = SAMLEngineUtils.generateAssertion(SAMLVersion.VERSION_20, SAMLEngineUtils.generateNCName(), SAMLEngineUtils.getCurrentTime(), issuerAssertion); final Subject subject = SAMLEngineUtils.generateSubject(); // Mandatory to be verified // String format = NameID.UNSPECIFIED // specification: 'SAML:2.0' exist // opensaml: "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" // opensaml "urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified" String format = request.getEidasNameidFormat(); if (format == null) { format = SAMLExtensionFormat.EIDAS10 == getExtensionProcessor().getFormat() ? EIDASAuthnRequest.NAMEID_FORMAT_PERSISTENT : EIDASAuthnRequest.NAMEID_FORMAT_UNSPECIFIED; } final String nameQualifier = ""; LOG.trace("Generate NameID"); final NameID nameId = SAMLEngineUtils.generateNameID(super.getSamlCoreProperties().getResponder(), format, nameQualifier); String nameIdValue = getUniquenessIdentifier(request, pal); nameId.setValue(nameIdValue); subject.setNameID(nameId); // Mandatory if urn:oasis:names:tc:SAML:2.0:cm:bearer. // Optional in other case. LOG.trace("Generate SubjectConfirmationData."); final SubjectConfirmationData dataBearer = SAMLEngineUtils.generateSubjectConfirmationData( SAMLEngineUtils.getCurrentTime(), request.getAssertionConsumerServiceURL(), request.getSamlId()); // Mandatory if urn:oasis:names:tc:SAML:2.0:cm:bearer. // Optional in other case. LOG.trace("Generate SubjectConfirmation"); final SubjectConfirmation subjectConf = SAMLEngineUtils .generateSubjectConfirmation(SubjectConfirmation.METHOD_BEARER, dataBearer); final List<SubjectConfirmation> listSubjectConf = new ArrayList<SubjectConfirmation>(); listSubjectConf.add(subjectConf); for (final Iterator<SubjectConfirmation> iter = listSubjectConf.iterator(); iter.hasNext();) { final SubjectConfirmation element = iter.next(); if (SubjectConfirmation.METHOD_BEARER.equals(element.getMethod())) { // ipAddress Mandatory if method is Bearer. if (StringUtils.isBlank(ipAddress)) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : ipAddress is null or empty"); throw new EIDASSAMLEngineException(EIDASErrors.INTERNAL_ERROR.errorCode(), EIDASErrors.INTERNAL_ERROR.errorCode(), "ipAddress is null or empty"); } element.getSubjectConfirmationData().setAddress(ipAddress.trim()); } element.getSubjectConfirmationData().setRecipient(request.getAssertionConsumerServiceURL()); element.getSubjectConfirmationData().setNotOnOrAfter(notOnOrAfter); } // The SAML 2.0 specification allows multiple SubjectConfirmations subject.getSubjectConfirmations().addAll(listSubjectConf); // Mandatory assertion.setSubject(subject); // Conditions that MUST be evaluated when assessing the validity of // and/or when using the assertion. final Conditions conditions = this.generateConditions(SAMLEngineUtils.getCurrentTime().minusMinutes(1), notOnOrAfter, request.getIssuer()); assertion.setConditions(conditions); LOG.trace("Generate Authentication Statement."); final AuthnStatement eidasAuthnStat = this.generateAuthStatement(ipAddress); assertion.getAuthnStatements().add(eidasAuthnStat); return assertion; } private String getUniquenessIdentifier(final EIDASAuthnRequest request, IPersonalAttributeList pal) throws EIDASSAMLEngineException { for (PersonalAttribute attribute : pal) { String attributeName = getAttributeName(attribute); if (EIDAS_NATURALPERSON_IDENTIFIER.equals(attributeName) && !attribute.isEmptyValue()) { return attribute.getValue().get(0); } if (EIDAS_LEGALPERSON_IDENTIFIER.equals(attributeName) && !attribute.isEmptyValue()) { return attribute.getValue().get(0); } } return request.getCountry() + UNIQUE_ID_POSTFIX; } private String getAttributeName(final PersonalAttribute attribute) throws EIDASSAMLEngineException { if (StringUtils.isBlank(attribute.getName())) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : {}", ATTRIBUTE_EMPTY_LITERAL); throw new EIDASSAMLEngineException(ATTRIBUTE_EMPTY_LITERAL); } final String attributeName = extensionProcessor.getAttributeFullName(this, attribute.getName()); if (StringUtils.isBlank(attributeName)) { LOG.info("BUSINESS EXCEPTION : Attribute name: {} it is not known.", attribute.getName()); throw new EIDASSAMLEngineException(EIDASErrors.INTERNAL_ERROR.errorCode(), EIDASErrors.INTERNAL_ERROR.errorCode(), "Attribute name: " + attribute.getName() + " it is not known."); } return attributeName; } /** * Generate attribute statement. * * @param personalAttrList the personal attribute list * @param isHashing the is hashing * * @return the attribute statement * * @throws EIDASSAMLEngineException the SAML engine exception * @throws IOException */ private AttributeStatement generateAttributeStatement(final IPersonalAttributeList personalAttrList, final boolean isHashing) throws EIDASSAMLEngineException { LOG.trace("Generate attribute statement"); final AttributeStatement attrStatement = (AttributeStatement) SAMLEngineUtils .createSamlObject(AttributeStatement.DEFAULT_ELEMENT_NAME); final List<Attribute> list = attrStatement.getAttributes(); if (extensionProcessor == null) { return null; } for (PersonalAttribute attribute : personalAttrList) { // Verification that only one value it's permitted, simple or // complex, not both. final boolean simpleEmpty = attribute.getValue() == null || attribute.getValue().isEmpty(); final boolean complexEmpty = attribute.getComplexValue() == null || attribute.getComplexValue().isEmpty(); setAttributeValues(attribute, list, simpleEmpty, complexEmpty, isHashing); } return attrStatement; } private void setAttributeValues(final PersonalAttribute attribute, List<Attribute> list, boolean simpleEmpty, boolean complexEmpty, final boolean isHashing) throws EIDASSAMLEngineException { String attributeName = getAttributeName(attribute); if (!simpleEmpty && !complexEmpty) { throw new EIDASSAMLEngineException(EIDASErrors.INTERNAL_ERROR.errorCode(), EIDASErrors.INTERNAL_ERROR.errorCode(), "Attribute name: " + attribute.getName() + " must be contain one value, simple or complex value."); } else { if (!simpleEmpty) { list.add(extensionProcessor.generateAttrSimple(attributeName, attribute.getStatus(), attribute.getValue(), isHashing)); } else if (!complexEmpty) { list.add(SAMLEngineUtils.generateAttrComplex(this, attributeName, attribute.getStatus(), attribute.getComplexValue(), isHashing)); } else if (attribute.getValue() != null) { list.add(extensionProcessor.generateAttrSimple(attributeName, attribute.getStatus(), new ArrayList<String>(), isHashing)); } else { // Add attribute complex. list.add(SAMLEngineUtils.generateAttrComplex(this, attributeName, attribute.getStatus(), new HashMap<String, String>(), isHashing)); } } } static CharsetEncoder encoder = Charset.forName("ISO-8859-1").newEncoder(); public static boolean needsTransliteration(String v) { return !encoder.canEncode(v); } /** * Generate conditions that MUST be evaluated when assessing the validity of * and/or when using the assertion. * * @param notBefore the not before * @param notOnOrAfter the not on or after * @param audienceURI the audience URI. * * @return the conditions */ private Conditions generateConditions(final DateTime notBefore, final DateTime notOnOrAfter, final String audienceURI) { LOG.trace("Generate conditions."); final Conditions conditions = (Conditions) SAMLEngineUtils .createSamlObject(Conditions.DEFAULT_ELEMENT_NAME); conditions.setNotBefore(notBefore); conditions.setNotOnOrAfter(notOnOrAfter); final AudienceRestriction restrictions = (AudienceRestriction) SAMLEngineUtils .createSamlObject(AudienceRestriction.DEFAULT_ELEMENT_NAME); final Audience audience = (Audience) SAMLEngineUtils.createSamlObject(Audience.DEFAULT_ELEMENT_NAME); audience.setAudienceURI(audienceURI); restrictions.getAudiences().add(audience); conditions.getAudienceRestrictions().add(restrictions); if (super.getSamlCoreProperties().isOneTimeUse()) { final OneTimeUse oneTimeUse = (OneTimeUse) SAMLEngineUtils .createSamlObject(OneTimeUse.DEFAULT_ELEMENT_NAME); conditions.getConditions().add(oneTimeUse); } return conditions; } private AttributeStatement findAttributeStatement(final Assertion assertion) throws EIDASSAMLEngineException { final List<XMLObject> listExtensions = assertion.getOrderedChildren(); boolean find = false; AttributeStatement requestedAttr = null; // Search the attribute statement. for (int i = 0; i < listExtensions.size() && !find; i++) { final XMLObject xml = listExtensions.get(i); if (xml instanceof AttributeStatement) { requestedAttr = (AttributeStatement) xml; find = true; } } if (!find) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : AttributeStatement it's not present."); throw new EIDASSAMLEngineException(EIDASErrors.INTERNAL_ERROR.errorCode(), EIDASErrors.INTERNAL_ERROR.errorCode(), "AttributeStatement it's not present."); } return requestedAttr; } private String computeSimpleValue(XSAnyImpl xmlString) { TransformerFactory transFactory = TransformerFactory.newInstance(); Transformer transformer = null; try { transformer = transFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); } catch (TransformerConfigurationException e) { LOG.warn(SAML_EXCHANGE, "ERROR : transformer configuration exception", e); } StringWriter buffer = new StringWriter(); try { if (transformer != null && xmlString != null && xmlString.getUnknownXMLObjects() != null && !xmlString.getUnknownXMLObjects().isEmpty()) { transformer.transform(new DOMSource(xmlString.getUnknownXMLObjects().get(0).getDOM()), new StreamResult(buffer)); } } catch (TransformerException e) { LOG.warn(SAML_EXCHANGE, "ERROR : transformer exception", e); } return buffer.toString(); } private Map<String, String> computeComplexValue(final XSAnyImpl complexValue) { final Map<String, String> multiValues = new HashMap<String, String>(); for (int nextComplexValue = 0; nextComplexValue < complexValue.getUnknownXMLObjects() .size(); nextComplexValue++) { final XSAnyImpl simple = (XSAnyImpl) complexValue.getUnknownXMLObjects().get(nextComplexValue); multiValues.put(simple.getElementQName().getLocalPart(), simple.getTextContent()); } return multiValues; } /** * Generate personal attribute list. * * @param assertion the assertion * * @return the personal attribute list * * @throws EIDASSAMLEngineException the SAML engine exception */ private IPersonalAttributeList generatePersonalAttributeList(final Assertion assertion) throws EIDASSAMLEngineException { LOG.trace("Generate personal attribute list from XMLObject."); AttributeStatement requestedAttr = findAttributeStatement(assertion); final List<Attribute> reqAttrs = requestedAttr.getAttributes(); final IPersonalAttributeList personalAttrList = new PersonalAttributeList(); String attributeName; // Process the attributes. for (int nextAttribute = 0; nextAttribute < reqAttrs.size(); nextAttribute++) { final Attribute attribute = reqAttrs.get(nextAttribute); final PersonalAttribute personalAttribute = new PersonalAttribute(); attributeName = attribute.getName(); personalAttribute.setName(attributeName.substring(attributeName.lastIndexOf('/') + 1)); personalAttribute.setStatus(attribute.getUnknownAttributes() .get(new QName(getExtensionProcessor().getFormat().getAssertionNS(), "AttributeStatus", getExtensionProcessor().getFormat().getAssertionPrefix()))); final List<String> simpleValues = new ArrayList<String>(); final Map<String, String> multiValues = new HashMap<String, String>(); final List<XMLObject> values = attribute.getOrderedChildren(); // Process the values. for (int nextValue = 0; nextValue < values.size(); nextValue++) { final XMLObject xmlObject = values.get(nextValue); if (xmlObject instanceof XSStringImpl) { // Process simple value. simpleValues.add(((XSStringImpl) xmlObject).getValue()); } else if (xmlObject instanceof XSAnyImpl) { if ("http://www.stork.gov.eu/1.0/signedDoc".equals(attributeName)) { final XSAnyImpl xmlString = (XSAnyImpl) values.get(nextValue); simpleValues.add(computeSimpleValue(xmlString)); } else if ("http://www.stork.gov.eu/1.0/canonicalResidenceAddress".equals(attributeName)) { // Process complex value. final XSAnyImpl complexValue = (XSAnyImpl) xmlObject; multiValues.putAll(computeComplexValue(complexValue)); } else { // Process simple value. simpleValues.add(((XSAnyImpl) xmlObject).getTextContent()); } } else if (xmlObject instanceof GenericEidasAttributeType) { // Process simple value. simpleValues.add(((GenericEidasAttributeType) xmlObject).getValue()); } else { LOG.info("BUSINESS EXCEPTION : attribute value is unknown in generatePersonalAttributeList."); throw new EIDASSAMLEngineException(EIDASErrors.INTERNAL_ERROR.errorCode(), EIDASErrors.INTERNAL_ERROR.errorCode(), "Attribute value it's unknown."); } } personalAttribute.setValue(simpleValues); personalAttribute.setComplexValue(multiValues); personalAttrList.add(personalAttribute); } return personalAttrList; } /** * Generate the authentication request. * * @param request the request that contain all parameters for generate an * authentication request. * * @return the EIDAS authentication request that has been processed. * * @throws EIDASSAMLEngineException the EIDASSAML engine exception */ public EIDASAuthnRequest generateEIDASAuthnRequest(final EIDASAuthnRequest request) throws EIDASSAMLEngineException { LOG.trace("Generate SAMLAuthnRequest."); if (request == null) { LOG.debug(SAML_EXCHANGE, "Sign and Marshall - null input"); LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Sign and Marshall -null input"); throw new EIDASSAMLEngineException(EIDASErrors.INTERNAL_ERROR.errorCode(), EIDASErrors.INTERNAL_ERROR.errorMessage()); } selectFormat(request.getMessageFormatName()); // Validate Parameters mandatories validateParamAuthnReq(request); final AuthnRequest authnRequestAux = SAMLEngineUtils.generateSAMLAuthnRequest( SAMLEngineUtils.generateNCName(), SAMLVersion.VERSION_20, SAMLEngineUtils.getCurrentTime()); // Set name spaces. setRequestNameSpaces(authnRequestAux); // Add parameter Mandatory authnRequestAux.setForceAuthn(Boolean.TRUE); // Add parameter Mandatory authnRequestAux.setIsPassive(Boolean.FALSE); authnRequestAux.setAssertionConsumerServiceURL(request.getAssertionConsumerServiceURL()); authnRequestAux.setProviderName(request.getProviderName()); // Add protocol binding authnRequestAux.setProtocolBinding(getProtocolBinding(request.getBinding())); // Add parameter optional // Destination is mandatory // The application must to know the destination if (StringUtils.isNotBlank(request.getDestination())) { authnRequestAux.setDestination(request.getDestination()); } // Consent is optional. Set from SAMLEngine.xml - consent. authnRequestAux.setConsent(super.getSamlCoreProperties().getConsentAuthnRequest()); final Issuer issuer = SAMLEngineUtils.generateIssuer(); if (request.getIssuer() != null) { issuer.setValue(SAMLEngineUtils.getValidIssuerValue(request.getIssuer())); } else { issuer.setValue(super.getSamlCoreProperties().getRequester()); } // Optional final String formatEntity = super.getSamlCoreProperties().getFormatEntity(); if (StringUtils.isNotBlank(formatEntity)) { issuer.setFormat(formatEntity); } authnRequestAux.setIssuer(issuer); addAuthnContext(request, authnRequestAux); // Generate format extensions. final Extensions formatExtensions = getExtensionProcessor().generateExtensions(this, request); // add the extensions to the SAMLAuthnRequest authnRequestAux.setExtensions(formatExtensions); addNameIDPolicy(authnRequestAux, request.getEidasNameidFormat()); // the result contains an authentication request token (byte[]), // identifier of the token, and all parameters from the request. final EIDASAuthnRequest authRequest = getExtensionProcessor() .processExtensions(authnRequestAux.getExtensions()); authRequest.setMessageFormatName(getExtensionProcessor().getFormat().getName()); try { authRequest.setTokenSaml( super.signAndMarshall(authnRequestAux, getExtensionProcessor().getFormat().getName())); } catch (SAMLEngineException e) { LOG.debug(SAML_EXCHANGE, "Sign and Marshall.", e); LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Sign and Marshall.", e); throw new EIDASSAMLEngineException(EIDASErrors.INTERNAL_ERROR.errorCode(), EIDASErrors.INTERNAL_ERROR.errorMessage(), e); } authRequest.setSamlId(authnRequestAux.getID()); authRequest.setDestination(authnRequestAux.getDestination()); authRequest.setAssertionConsumerServiceURL(authnRequestAux.getAssertionConsumerServiceURL()); authRequest.setProviderName(authnRequestAux.getProviderName()); authRequest.setIssuer(authnRequestAux.getIssuer().getValue()); authRequest.setBinding(request.getBinding()); authRequest.setOriginalIssuer(request.getOriginalIssuer()); authRequest.setEidasLoACompareType(request.getEidasLoACompareType()); authRequest.setEidasLoA(request.getEidasLoA()); authRequest.setEidasNameidFormat(request.getEidasNameidFormat()); return authRequest; } private void selectFormat(String selectedFormat) { if (!StringUtils.isEmpty(selectedFormat) && SAMLExtensionFormat.AVAILABLE_FORMATS.containsKey(selectedFormat)) { for (ExtensionProcessorI proc : availableExtensionProcessors) { if (proc.getFormat() == SAMLExtensionFormat.AVAILABLE_FORMATS.get(selectedFormat)) { setExtensionProcessor(proc); break; } } } } private void addNameIDPolicy(final AuthnRequest authnRequestAux, final String selectedNameID) { if (extensionProcessor != null && extensionProcessor.getFormat() == SAMLExtensionFormat.EIDAS10 && !StringUtils.isEmpty(selectedNameID)) { NameIDPolicy policy = (NameIDPolicy) SAMLEngineUtils .createSamlObject(NameIDPolicy.DEFAULT_ELEMENT_NAME); policy.setFormat(selectedNameID); policy.setAllowCreate(true); authnRequestAux.setNameIDPolicy(policy); } } private void addAuthnContext(final EIDASAuthnRequest request, AuthnRequest authnRequestAux) throws EIDASSAMLEngineException { if (StringUtils.isEmpty(request.getEidasLoA())) { return; } if (EidasLoaLevels.getLevel(request.getEidasLoA()) == null) { throw new EIDASSAMLEngineException(EIDASErrors.COLLEAGUE_REQ_INVALID_LOA.errorCode(), EIDASErrors.COLLEAGUE_REQ_INVALID_LOA.errorMessage()); } RequestedAuthnContext authnContext = (RequestedAuthnContext) SAMLEngineUtils .createSamlObject(RequestedAuthnContext.DEFAULT_ELEMENT_NAME); authnContext.setComparison(SAMLEngineUtils .getAuthnCtxtComparisonType(EidasLoaCompareType.getCompareType(request.getEidasLoACompareType()))); AuthnContextClassRef authnContextClassRef = (AuthnContextClassRef) SAMLEngineUtils .createSamlObject(AuthnContextClassRef.DEFAULT_ELEMENT_NAME); authnContextClassRef.setAuthnContextClassRef(request.getEidasLoA()); authnContext.getAuthnContextClassRefs().add(authnContextClassRef); authnRequestAux.setRequestedAuthnContext(authnContext); } public EIDASAuthnResponse generateEIDASAuthnResponse(final EIDASAuthnRequest request, final EIDASAuthnResponse responseAuthReq, final String ipAddress, final boolean isHashing) throws EIDASSAMLEngineException { return generateEIDASAuthnResponse(request, responseAuthReq, ipAddress, isHashing, false); } /** * Generate authentication response in one of the supported formats. * * @param request the request * @param responseAuthReq the response authentication request * @param ipAddress the IP address * @param isHashing the is hashing * @param signAssertion whether to sign the attribute assertion * * @return the authentication response * * @throws EIDASSAMLEngineException the EIDASSAML engine exception */ public EIDASAuthnResponse generateEIDASAuthnResponse(final EIDASAuthnRequest request, final EIDASAuthnResponse responseAuthReq, final String ipAddress, final boolean isHashing, final boolean signAssertion) throws EIDASSAMLEngineException { LOG.trace("generateEIDASAuthnResponse"); //if not setted before if (StringUtils.isEmpty(getCountryRespondTo())) { setCountryRespondTo(request.getCountry()); } // Validate parameters validateParamResponse(request, responseAuthReq); // Mandatory SAML LOG.trace("Generate StatusCode"); final StatusCode statusCode = SAMLEngineUtils.generateStatusCode(StatusCode.SUCCESS_URI); LOG.trace("Generate Status"); final Status status = SAMLEngineUtils.generateStatus(statusCode); LOG.trace("Generate StatusMessage"); final StatusMessage statusMessage = (StatusMessage) SAMLEngineUtils .generateStatusMessage(StatusCode.SUCCESS_URI); status.setStatusMessage(statusMessage); LOG.trace("Generate Response"); // RESPONSE final Response response = genAuthnRespBase(status, request.getAssertionConsumerServiceURL(), request.getSamlId()); if (responseAuthReq.getIssuer() != null && !responseAuthReq.getIssuer().isEmpty() && response.getIssuer() != null) { response.getIssuer().setValue(SAMLEngineUtils.getValidIssuerValue(responseAuthReq.getIssuer())); } DateTime notOnOrAfter = new DateTime(); notOnOrAfter = notOnOrAfter.plusSeconds(super.getSamlCoreProperties().getTimeNotOnOrAfter()); final Assertion assertion = this.generateAssertion(ipAddress, request, response, responseAuthReq.getPersonalAttributeList(), notOnOrAfter); final AttributeStatement attrStatement = this .generateAttributeStatement(responseAuthReq.getPersonalAttributeList(), isHashing); assertion.getAttributeStatements().add(attrStatement); addAuthnContextClassRef(responseAuthReq, assertion); // Add assertions Assertion signedAssertion = null; if (signAssertion) { try { signedAssertion = (Assertion) super.sign(assertion, getExtensionProcessor().getFormat().getName()); } catch (SAMLEngineException exc) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : cannot sign assertion: {}", exc.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : cannot sign assertion: {}", exc); } } response.getAssertions().add(signedAssertion == null ? assertion : signedAssertion); final EIDASAuthnResponse authresponse = new EIDASAuthnResponse(); try { authresponse .setTokenSaml(super.signAndMarshall(response, getExtensionProcessor().getFormat().getName())); authresponse.setSamlId(response.getID()); } catch (SAMLEngineException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Sign and Marshall.", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : Sign and Marshall.", e); throw new EIDASSAMLEngineException(EIDASErrors.INTERNAL_ERROR.errorCode(), EIDASErrors.INTERNAL_ERROR.errorMessage(), e); } return authresponse; } private void addAuthnContextClassRef(final EIDASAuthnResponse responseAuthReq, final Assertion assertion) { if (!StringUtils.isEmpty(responseAuthReq.getAssuranceLevel())) { AuthnContextClassRef authnContextClassRef = assertion.getAuthnStatements().get(0).getAuthnContext() .getAuthnContextClassRef(); if (authnContextClassRef == null) { authnContextClassRef = (AuthnContextClassRef) SAMLEngineUtils .createSamlObject(AuthnContextClassRef.DEFAULT_ELEMENT_NAME); assertion.getAuthnStatements().get(0).getAuthnContext() .setAuthnContextClassRef(authnContextClassRef); } authnContextClassRef.setAuthnContextClassRef(responseAuthReq.getAssuranceLevel()); } } /** * Generate authentication response fail. * * @param request the request * @param response the response * @param ipAddress the IP address * @param isHashing the is hashing * * @return the authentication response * * @throws EIDASSAMLEngineException the EIDASSAML engine exception */ public EIDASAuthnResponse generateEIDASAuthnResponseFail(final EIDASAuthnRequest request, final EIDASAuthnResponse response, final String ipAddress, final boolean isHashing) throws EIDASSAMLEngineException { LOG.trace("generateEIDASAuthnResponseFail"); if (StringUtils.isEmpty(getCountryRespondTo())) { setCountryRespondTo(request.getCountry()); } validateParamResponseFail(request, response); // Mandatory final StatusCode statusCode = SAMLEngineUtils.generateStatusCode(response.getStatusCode()); // Mandatory SAML LOG.trace("Generate StatusCode."); // Subordinate code it's optional in case not covered into next codes: // - urn:oasis:names:tc:SAML:2.0:status:AuthnFailed // - urn:oasis:names:tc:SAML:2.0:status:InvalidAttrNameOrValue // - urn:oasis:names:tc:SAML:2.0:status:InvalidNameIDPolicy // - urn:oasis:names:tc:SAML:2.0:status:RequestDenied // - http://www.stork.gov.eu/saml20/statusCodes/QAANotSupported if (StringUtils.isNotBlank(response.getSubStatusCode())) { final StatusCode newStatusCode = SAMLEngineUtils.generateStatusCode(response.getSubStatusCode()); statusCode.setStatusCode(newStatusCode); } LOG.debug("Generate Status."); final Status status = SAMLEngineUtils.generateStatus(statusCode); if (StringUtils.isNotBlank(response.getMessage())) { final StatusMessage statusMessage = (StatusMessage) SAMLEngineUtils .generateStatusMessage(response.getMessage()); status.setStatusMessage(statusMessage); } LOG.trace("Generate Response."); // RESPONSE final Response responseFail = genAuthnRespBase(status, request.getAssertionConsumerServiceURL(), request.getSamlId()); if (response.getIssuer() != null && !response.getIssuer().isEmpty() && response.getIssuer() != null) { responseFail.getIssuer().setValue(response.getIssuer()); } DateTime notOnOrAfter = new DateTime(); notOnOrAfter = notOnOrAfter.plusSeconds(super.getSamlCoreProperties().getTimeNotOnOrAfter()); final Assertion assertion = this.generateAssertion(ipAddress, request, responseFail, new PersonalAttributeList(), notOnOrAfter); addAuthnContextClassRef(response, assertion); responseFail.getAssertions().add(assertion); LOG.trace("Sign and Marshall ResponseFail."); final EIDASAuthnResponse eidasResponse = new EIDASAuthnResponse(); try { eidasResponse.setTokenSaml( super.signAndMarshall(responseFail, getExtensionProcessor().getFormat().getName())); eidasResponse.setSamlId(responseFail.getID()); } catch (SAMLEngineException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : SAMLEngineException.", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : SAMLEngineException.", e); throw new EIDASSAMLEngineException(EIDASErrors.INTERNAL_ERROR.errorCode(), EIDASErrors.INTERNAL_ERROR.errorMessage(), e); } return eidasResponse; } /** * Generate authentication statement. * * @param ipAddress the IP address * * @return the authentication statement */ private AuthnStatement generateAuthStatement(final String ipAddress) { LOG.trace("Generate authenticate statement."); final SubjectLocality subjectLocality = SAMLEngineUtils.generateSubjectLocality(ipAddress); final AuthnContext authnContext = (AuthnContext) SAMLEngineUtils .createSamlObject(AuthnContext.DEFAULT_ELEMENT_NAME); final AuthnContextDecl authnContextDecl = (AuthnContextDecl) SAMLEngineUtils .createSamlObject(AuthnContextDecl.DEFAULT_ELEMENT_NAME); authnContext.setAuthnContextDecl(authnContextDecl); final AuthnStatement authnStatement = SAMLEngineUtils.generateAthnStatement(new DateTime(), authnContext); // Optional authnStatement.setSessionIndex(null); authnStatement.setSubjectLocality(subjectLocality); return authnStatement; } /** * Gets the alias from X.509 Certificate at keystore. * * @param keyInfo the key info * @param ownKeyStore * @param ownKeyStore * * @return the alias */ private String getAlias(final KeyInfo keyInfo, KeyStore ownKeyStore) { LOG.trace("Recover alias information"); String alias = null; try { final org.opensaml.xml.signature.X509Certificate xmlCert = keyInfo.getX509Datas().get(0) .getX509Certificates().get(0); // Transform the KeyInfo to X509Certificate. CertificateFactory certFact; certFact = CertificateFactory.getInstance("X.509"); final ByteArrayInputStream bis = new ByteArrayInputStream(Base64.decode(xmlCert.getValue())); final X509Certificate cert = (X509Certificate) certFact.generateCertificate(bis); final String tokenSerialNumber = cert.getSerialNumber().toString(HEXA); final X500Name tokenIssuerDN = new X500Name(cert.getIssuerDN().getName()); String aliasCert; X509Certificate certificate; boolean find = false; for (final Enumeration<String> e = ownKeyStore.aliases(); e.hasMoreElements() && !find;) { aliasCert = e.nextElement(); certificate = (X509Certificate) ownKeyStore.getCertificate(aliasCert); final String serialNum = certificate.getSerialNumber().toString(HEXA); X500Name issuerDN = new X500Name(certificate.getIssuerDN().getName()); if (serialNum.equalsIgnoreCase(tokenSerialNumber) && X500PrincipalUtil.principalEquals(issuerDN, tokenIssuerDN)) { alias = aliasCert; find = true; } } } catch (KeyStoreException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Procces getAlias from certificate associated into the signing keystore: {}", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : Procces getAlias from certificate associated into the signing keystore: {}", e); } catch (CertificateException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Procces getAlias from certificate associated into the signing keystore: {}", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : Procces getAlias from certificate associated into the signing keystore: {}", e); } catch (RuntimeException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Procces getAlias from certificate associated into the signing keystore: {}", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : Procces getAlias from certificate associated into the signing keystore: {}", e); } return alias; } /** * Gets the country from X.509 Certificate. * * @param keyInfo the key info * * @return the country */ private String getCountry(final KeyInfo keyInfo) { LOG.trace("Recover country information."); String result = ""; try { final org.opensaml.xml.signature.X509Certificate xmlCert = keyInfo.getX509Datas().get(0) .getX509Certificates().get(0); // Transform the KeyInfo to X509Certificate. CertificateFactory certFact; certFact = CertificateFactory.getInstance("X.509"); final ByteArrayInputStream bis = new ByteArrayInputStream(Base64.decode(xmlCert.getValue())); final X509Certificate cert = (X509Certificate) certFact.generateCertificate(bis); String distName = cert.getSubjectDN().toString(); distName = StringUtils.deleteWhitespace(StringUtils.upperCase(distName)); final String countryCode = "C="; final int init = distName.indexOf(countryCode); if (init > StringUtils.INDEX_NOT_FOUND) { // Exist country code. int end = distName.indexOf(',', init); if (end <= StringUtils.INDEX_NOT_FOUND) { end = distName.length(); } if (init < end && end > StringUtils.INDEX_NOT_FOUND) { result = distName.substring(init + countryCode.length(), end); //It must be a two characters value if (result.length() > 2) { result = result.substring(0, 2); } } } } catch (CertificateException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Procces getCountry from certificate. {}", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : Procces getCountry from certificate. {}", e); } return result.trim(); } /** * Sets the name spaces. TODO: may be moved to extension processor * * @param tokenSaml the new name spaces */ private void setRequestNameSpaces(final XMLObject tokenSaml) { LOG.trace("Set namespaces."); tokenSaml.getNamespaceManager() .registerNamespace(new Namespace(SAMLConstants.SAML20_NS, SAMLConstants.SAML20_PREFIX)); tokenSaml.getNamespaceManager() .registerNamespace(new Namespace("http://www.w3.org/2000/09/xmldsig#", "ds")); tokenSaml.getNamespaceManager() .registerNamespace(new Namespace(SAMLConstants.SAML20P_NS, SAMLConstants.SAML20P_PREFIX)); if (this.getExtensionProcessor() instanceof StorkExtensionProcessor) { tokenSaml.getNamespaceManager().registerNamespace( new Namespace(SAMLCore.STORK10_NS.getValue(), SAMLCore.STORK10_PREFIX.getValue())); tokenSaml.getNamespaceManager().registerNamespace( new Namespace(SAMLCore.STORK10P_NS.getValue(), SAMLCore.STORK10P_PREFIX.getValue())); } if (this.getExtensionProcessor() instanceof EidasExtensionProcessor) { tokenSaml.getNamespaceManager().registerNamespace( new Namespace(SAMLCore.EIDAS10_SAML_NS.getValue(), SAMLCore.EIDAS10_SAML_PREFIX.getValue())); } } /** * Sets the name spaces. * * @param tokenSaml the new name spaces */ private void setResponseNameSpaces(final XMLObject tokenSaml) { LOG.trace("Set namespaces."); tokenSaml.getNamespaceManager() .registerNamespace(new Namespace(SAMLConstants.SAML20_NS, SAMLConstants.SAML20_PREFIX)); tokenSaml.getNamespaceManager() .registerNamespace(new Namespace("http://www.w3.org/2000/09/xmldsig#", "ds")); tokenSaml.getNamespaceManager() .registerNamespace(new Namespace(SAMLConstants.SAML20P_NS, SAMLConstants.SAML20P_PREFIX)); tokenSaml.getNamespaceManager().registerNamespace( new Namespace(SAMLCore.STORK10_NS.getValue(), SAMLCore.STORK10_PREFIX.getValue())); tokenSaml.getNamespaceManager().registerNamespace( new Namespace(SAMLCore.STORK10P_NS.getValue(), SAMLCore.STORK10P_PREFIX.getValue())); if (this.getExtensionProcessor() instanceof EidasExtensionProcessor) { tokenSaml.getNamespaceManager().registerNamespace(new Namespace( SAMLCore.EIDAS10_RESPONSESAML_NS.getValue(), SAMLCore.EIDAS10_SAML_PREFIX.getValue())); } } /** * Validate parameters from authentication request. * * @param request the request. * * @throws EIDASSAMLEngineException the EIDASSAML engine exception */ private void validateParamAuthnReq(final EIDASAuthnRequest request) throws EIDASSAMLEngineException { LOG.trace("Validate parameters from authentication request."); // URL to which Authentication Response must be sent. if (getExtensionProcessor().getFormat() == SAMLExtensionFormat.STORK10 && StringUtils.isBlank(request.getAssertionConsumerServiceURL())) { throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "SamlEngine: Assertion Consumer Service URL is mandatory."); } // the name of the original service provider requesting the // authentication. if (StringUtils.isBlank(request.getProviderName())) { throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "SamlEngine: Service Provider is mandatory."); } // object that contain all attributes requesting. if (request.getPersonalAttributeList() == null || request.getPersonalAttributeList().isEmpty()) { throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "attributeQueries is null or empty."); } // Quality authentication assurance level. int qaa = request.getQaa(); if (getExtensionProcessor().getFormat() == SAMLExtensionFormat.STORK10 && (qaa < QAAAttribute.MIN_VALUE) || (qaa > QAAAttribute.MAX_VALUE)) { throw new EIDASSAMLEngineException(EIDASErrors.QAALEVEL.errorCode(), EIDASErrors.QAALEVEL.errorCode(), "Qaal: " + request.getQaa() + ", is invalid."); } } /** * Validate parameters from response. * * @param request the request * @param responseAuthReq the response authentication request * * @throws EIDASSAMLEngineException the EIDASSAML engine exception */ private void validateParamResponse(final EIDASAuthnRequest request, final EIDASAuthnResponse responseAuthReq) throws EIDASSAMLEngineException { LOG.trace("Validate parameters response."); if (StringUtils.isBlank(request.getIssuer())) { throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "Issuer must be not empty or null."); } if (responseAuthReq.getPersonalAttributeList() == null || responseAuthReq.getPersonalAttributeList().isEmpty()) { LOG.error(SAML_EXCHANGE, "PersonalAttributeList is null or empty."); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "PersonalAttributeList is null or empty."); } if (StringUtils.isBlank(request.getAssertionConsumerServiceURL())) { throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "assertionConsumerServiceURL is null or empty."); } if (StringUtils.isBlank(request.getSamlId())) { throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "request ID is null or empty."); } initResponseProcessor(request); } private void initResponseProcessor(final EIDASAuthnRequest request) { if (extensionProcessor == null && !StringUtils.isEmpty(request.getMessageFormatName())) { for (ExtensionProcessorI extensionProcessorI : getExtensionProcessors()) { if (request.getMessageFormatName().equalsIgnoreCase(extensionProcessorI.getFormat().getName())) { setExtensionProcessor(extensionProcessorI); break; } } } if (LOG.isDebugEnabled()) { LOG.debug("initResponseProcessor: Message format is " + (extensionProcessor == null ? null : extensionProcessor.getFormat().getName())); } } /** * Validate parameter from response fail. * * @param request the request * @param response the response * * @throws EIDASSAMLEngineException the EIDASSAML engine exception */ private void validateParamResponseFail(final EIDASAuthnRequest request, final EIDASAuthnResponse response) throws EIDASSAMLEngineException { LOG.trace("Validate parameters response fail."); if (StringUtils.isBlank(response.getStatusCode())) { throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "Code error it's null or empty."); } if (StringUtils.isBlank(request.getAssertionConsumerServiceURL())) { throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "assertionConsumerServiceURL is null or empty."); } if (StringUtils.isBlank(request.getSamlId())) { throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "request ID is null or empty."); } initResponseProcessor(request); } /** * Validate authentication request. * * @param tokenSaml the token SAML * * @return the authentication request * * @throws EIDASSAMLEngineException the EIDASSAML engine exception */ public EIDASAuthnRequest validateEIDASAuthnRequest(final byte[] tokenSaml) throws EIDASSAMLEngineException { LOG.trace("validateEIDASAuthnRequest"); if (tokenSaml == null) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Saml authentication request is null."); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "Saml authentication request is null."); } validateSchema(new String(tokenSaml, Charset.forName("UTF-8"))); final AuthnRequest samlRequest = validateRequestHelper(tokenSaml); LOG.debug("Generate EIDASAuthnRequest."); final EIDASAuthnRequest authnRequest = getExtensionProcessor() .processExtensions(samlRequest.getExtensions()); if (samlRequest.getSignature() != null) { authnRequest.setCountry(this.getCountry(samlRequest.getSignature().getKeyInfo())); authnRequest.setAlias( this.getAlias(samlRequest.getSignature().getKeyInfo(), super.getSigner().getTrustStore())); } extractLoA(samlRequest, authnRequest); authnRequest.setSamlId(samlRequest.getID()); authnRequest.setDestination(samlRequest.getDestination()); authnRequest.setAssertionConsumerServiceURL(samlRequest.getAssertionConsumerServiceURL()); authnRequest.setProviderName(samlRequest.getProviderName()); authnRequest.setIssuer(samlRequest.getIssuer().getValue()); authnRequest.setBinding(SAMLEngineUtils.getBindingMethod(samlRequest.getProtocolBinding())); authnRequest.setEidasNameidFormat( samlRequest.getNameIDPolicy() == null ? null : samlRequest.getNameIDPolicy().getFormat()); authnRequest.setMessageFormatName(getExtensionProcessor().getFormat().getName()); //Delete unknown elements from requested ones final Iterator<PersonalAttribute> iterator = authnRequest.getPersonalAttributeList().iterator(); IPersonalAttributeList cleanPerAttrList = (PersonalAttributeList) authnRequest.getPersonalAttributeList(); LOG.debug("boucle des attributs nb=" + authnRequest.getPersonalAttributeList().size()); while (iterator.hasNext()) { final PersonalAttribute attribute = iterator.next(); LOG.debug("extensionProcessor attribute=" + attribute.getName()); // Verify if the attribute name exists. final String attributeName = extensionProcessor.getAttributeFullName(this, attribute.getName()); if (StringUtils.isBlank(attributeName)) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Attribute name: {} was not found. It will be removed from the request object", attribute.getName()); cleanPerAttrList.remove(attribute.getName()); } } authnRequest.setPersonalAttributeList(cleanPerAttrList); LOG.debug("Fin validateEIDASAuthnRequest attribute nb=" + authnRequest.getPersonalAttributeList().size()); return authnRequest; } private void extractLoA(AuthnRequest samlRequest, EIDASAuthnRequest authnRequest) throws EIDASSAMLEngineException { if (samlRequest.getRequestedAuthnContext() != null && !samlRequest.getRequestedAuthnContext().getAuthnContextClassRefs().isEmpty()) { RequestedAuthnContext rac = samlRequest.getRequestedAuthnContext(); if (null == rac.getComparison()) { throw new EIDASSAMLEngineException(EIDASErrors.INVALID_LOA_VALUE.errorCode(), EIDASErrors.INVALID_LOA_VALUE.errorMessage()); } String comparison = rac.getComparison().toString(); List<AuthnContextClassRef> authnContexts = rac.getAuthnContextClassRefs(); for (AuthnContextClassRef contextRef : authnContexts) { EidasLoaLevels level = EidasLoaLevels.getLevel(contextRef.getAuthnContextClassRef()); if (level != null && EidasLoaCompareType.getCompareType(comparison) != null) { authnRequest.setEidasLoA(level.stringValue()); authnRequest .setEidasLoACompareType(EidasLoaCompareType.getCompareType(comparison).stringValue()); break; } else if (!StringUtils.isEmpty(contextRef.getAuthnContextClassRef())) { throw new EIDASSAMLEngineException(EIDASErrors.INVALID_LOA_VALUE.errorCode(), EIDASErrors.INVALID_LOA_VALUE.errorMessage()); } } } } private AuthnRequest validateRequestHelper(final byte[] tokenSaml) throws EIDASSAMLEngineException { LOG.debug("Validate AuthnRequest extensionProcessor null?" + (extensionProcessor != null) + " nb extentionProc=" + getExtensionProcessors().length); AuthnRequest samlRequest = null; ExtensionProcessorI currentProcessor = null; Exception[] validationErrors = new Exception[getExtensionProcessors().length]; try { if (extensionProcessor != null) { try { samlRequest = validateRequestHelper(extensionProcessor, tokenSaml); } catch (ValidationException e) { throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorMessage(), e); } } for (int i = 0; samlRequest == null && i < getExtensionProcessors().length; i++) { try { ExtensionProcessorI trialExtensionProcessor = getExtensionProcessors()[i]; samlRequest = validateRequestHelper(trialExtensionProcessor, tokenSaml); } catch (ValidationException e) { validationErrors[i] = e; } catch (EIDASSAMLEngineException e) { if (EIDASErrors.SAML_ENGINE_NO_METADATA.errorCode().equalsIgnoreCase(e.getErrorCode())) { LOG.error("{} cannot retrieve metadata for validating request ", e.getErrorCode()); //continue; } else { validationErrors[i] = e; } } } } catch (EIDASSAMLEngineException e) { throw e; } catch (Exception e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : ValidationException: validate AuthRequest.", e.getMessage()); LOG.debug("ValidationException: validate AuthRequest.", e); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorMessage(), e); } if (samlRequest == null) { setExtensionProcessor(currentProcessor); ValidationException validationError = buildValidationError(validationErrors); if (validationError != null) { throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorMessage(), validationError); } else { throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorMessage()); } } LOG.debug("fin validateRequestHelper samlRequest null?" + (samlRequest == null)); return samlRequest; } private ValidationException buildValidationError(Exception[] validationErrors) { StringBuffer validationErrorMessage = new StringBuffer(); //some of the validation messages may appear several times (for each processor) Set<String> validationMessages = new HashSet<String>(); for (int i = 0; i < validationErrors.length; i++) { if (validationErrors[i] != null) { validationMessages.add(validationErrors[i].getMessage()); } } for (String message : validationMessages) { validationErrorMessage.append(message).append(VALIDATION_MESSAGE_SEPARATOR); } if (validationErrorMessage.length() > 0) { return new ValidationException(validationErrorMessage.toString()); } else { return null; } } private AuthnRequest validateRequestHelper(ExtensionProcessorI extensionProcessor, final byte[] tokenSaml) throws EIDASSAMLEngineException, ValidationException { AuthnRequest samlRequest = null; ValidatorSuite suite = Configuration.getValidatorSuite(extensionProcessor.getRequestValidatorId()); extensionProcessor.configureExtension(); samlRequest = (AuthnRequest) validateEidasSaml(tokenSaml, extensionProcessor.getFormat().getName()); try { suite.validate(samlRequest); if (tryProcessExtensions(extensionProcessor, samlRequest)) { setExtensionProcessor(extensionProcessor); LOG.debug("validation with " + extensionProcessor.getClass().getName() + " succeeded !!!"); return samlRequest; } else { samlRequest = null; } } catch (ValidationException e) { LOG.debug("validation with " + extensionProcessor.getClass().getName() + " not succeeded:", e); throw e; } return samlRequest; } private Response computeAuxResponse(final byte[] tokenSaml) throws EIDASSAMLEngineException { Response samlResponseAux = null; try { samlResponseAux = (Response) validateEidasSaml(tokenSaml, extensionProcessor == null ? null : extensionProcessor.getFormat().getName()); if (decryptResponse()) { /* In the @eu.eidas.encryption.SAMLAuthnResponseDecrypter.decryptSAMLResponse method when inserting the decrypted Assertions the DOM resets to null. Marsahlling it again resolves it. More info in the links belows https://jira.spring.io/browse/SES-148 http://digitaliser.dk/forum/2621692 */ super.noSignAndMarshall(samlResponseAux); } } catch (SAMLEngineException e) { LOG.warn("error validating the response ", e.getMessage()); LOG.debug("error validating the response", e); } return samlResponseAux; } private void validateSamlResponse(final Response samlResponse) throws EIDASSAMLEngineException { LOG.trace("Validate AuthnResponse"); ValidatorSuite suite = Configuration.getValidatorSuite(getExtensionProcessor().getResponseValidatorId()); try { suite.validate(samlResponse); } catch (ValidationException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : ValidationException: validate AuthResponse.", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : ValidationException: validate AuthResponse.", e); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorMessage(), e); } catch (Exception e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : ValidationException: validate AuthResponse.", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : ValidationException: validate AuthResponse.", e); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorMessage(), e); } } private EIDASAuthnResponse createEidasResponse(final Response samlResponse) { LOG.trace("Create EidasAuthResponse."); final EIDASAuthnResponse authnResponse = new EIDASAuthnResponse(); String pays = this.getCountry(samlResponse.getSignature().getKeyInfo()); LOG.debug("Pays du certificat signature=" + pays); if (authnResponse.getCountry().isEmpty() && !pays.isEmpty()) { //horreur !!! authnResponse.setCountry(pays); } LOG.trace("Set ID."); authnResponse.setSamlId(samlResponse.getID()); LOG.trace("Set InResponseTo."); authnResponse.setInResponseTo(samlResponse.getInResponseTo()); LOG.trace("Set statusCode."); authnResponse.setStatusCode(samlResponse.getStatus().getStatusCode().getValue()); // Subordinate code. if (samlResponse.getStatus().getStatusCode().getStatusCode() != null) { authnResponse.setSubStatusCode(samlResponse.getStatus().getStatusCode().getStatusCode().getValue()); } if (samlResponse.getStatus().getStatusMessage() != null) { LOG.trace("Set statusMessage."); authnResponse.setMessage(samlResponse.getStatus().getStatusMessage().getMessage()); } authnResponse.setEncrypted( samlResponse.getEncryptedAssertions() != null && !samlResponse.getEncryptedAssertions().isEmpty()); return authnResponse; } /** * Validate authentication response. * * @param tokenSaml the token SAML * @param userIP the user IP * * @return the authentication response * * @throws EIDASSAMLEngineException the EIDASSAML engine exception */ public EIDASAuthnResponse validateEIDASAuthnResponse(final byte[] tokenSaml, final String userIP, final long skewTimeInMillis) throws EIDASSAMLEngineException { LOG.trace("validateEIDASAuthnResponse"); if (tokenSaml == null) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Saml authentication response is null."); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "Saml authentication response is null."); } validateSchema(new String(tokenSaml, Charset.forName("UTF-8"))); final Response samlResponse = computeAuxResponse(tokenSaml); validateSamlResponse(samlResponse); final EIDASAuthnResponse authnResponse = createEidasResponse(samlResponse); LOG.trace("validateEidasResponse"); final Assertion assertion = validateEidasResponse(samlResponse, userIP, skewTimeInMillis); if (assertion != null) { LOG.trace("Set notOnOrAfter."); authnResponse.setNotOnOrAfter(assertion.getConditions().getNotOnOrAfter()); LOG.trace("Set notBefore."); authnResponse.setNotBefore(assertion.getConditions().getNotBefore()); authnResponse.setAudienceRestriction((assertion.getConditions().getAudienceRestrictions().get(0)) .getAudiences().get(0).getAudienceURI()); if (!assertion.getAuthnStatements().isEmpty() && assertion.getAuthnStatements().get(0).getAuthnContext() != null && assertion.getAuthnStatements().get(0).getAuthnContext().getAuthnContextClassRef() != null) { authnResponse.setAssuranceLevel(assertion.getAuthnStatements().get(0).getAuthnContext() .getAuthnContextClassRef().getAuthnContextClassRef()); } } // Case no error. if (assertion != null && StatusCode.SUCCESS_URI.equalsIgnoreCase(authnResponse.getStatusCode())) { LOG.trace("Status Success. Set PersonalAttributeList."); authnResponse.setPersonalAttributeList(generatePersonalAttributeList(assertion)); authnResponse.setFail(false); authnResponse.setAsserts(samlResponse.getAssertions()); } else { LOG.trace("Status Fail."); authnResponse.setFail(true); } LOG.trace("Return result."); return authnResponse; } /** * Validate response. * * @param samlResponse the SAML response * @param userIP the user IP * * @return the assertion * * @throws EIDASSAMLEngineException the EIDASSAML engine exception */ private Assertion validateEidasResponse(final Response samlResponse, final String userIP, final Long skewTimeInMillis) throws EIDASSAMLEngineException { // Exist only one Assertion if (samlResponse.getAssertions() == null || samlResponse.getAssertions().isEmpty()) { //in replace of throwing EIDASSAMLEngineException("Assertion is null or empty.") LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Assertion is null, empty or the response is encrypted and the decryption is not active."); return null; } final Assertion assertion = (Assertion) samlResponse.getAssertions().get(0); verifyMethodBearer(userIP, assertion); // Applying skew time conditions before testing it DateTime skewedNotBefore = new DateTime( assertion.getConditions().getNotBefore().getMillis() - skewTimeInMillis, DateTimeZone.UTC); DateTime skewedNotOnOrAfter = new DateTime( assertion.getConditions().getNotOnOrAfter().getMillis() + skewTimeInMillis, DateTimeZone.UTC); LOG.debug(SAML_EXCHANGE, "skewTimeInMillis : {}", skewTimeInMillis); LOG.debug(SAML_EXCHANGE, "skewedNotBefore : {}", skewedNotBefore); LOG.debug(SAML_EXCHANGE, "skewedNotOnOrAfter : {}", skewedNotOnOrAfter); assertion.getConditions().setNotBefore(skewedNotBefore); assertion.getConditions().setNotOnOrAfter(skewedNotOnOrAfter); verifyConditions(assertion); return assertion; } private void verifyConditions(Assertion assertion) throws EIDASSAMLEngineException { Conditions conditions = assertion.getConditions(); final DateTime serverDate = clock.getCurrentTime(); LOG.debug("serverDate : " + serverDate); if (conditions.getAudienceRestrictions() == null || conditions.getAudienceRestrictions().isEmpty()) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : AudienceRestriction must be present"); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "AudienceRestriction must be present"); } if (conditions.getOneTimeUse() == null) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : OneTimeUse must be present"); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "OneTimeUse must be present"); } if (conditions.getNotBefore() == null) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : NotBefore must be present"); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "NotBefore must be present"); } if (conditions.getNotBefore().isAfter(serverDate)) { LOG.error(SAML_EXCHANGE, "BUSINESS EXCEPTION : Current time is before NotBefore condition"); } if (conditions.getNotOnOrAfter() == null) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : NotOnOrAfter must be present"); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "NotOnOrAfter must be present"); } if (conditions.getNotOnOrAfter().isBeforeNow()) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Current time is after NotOnOrAfter condition"); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "Current time is after NotOnOrAfter condition"); } if (assertion.getConditions().getNotOnOrAfter().isBefore(serverDate)) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Token date expired (getNotOnOrAfter = " + assertion.getConditions().getNotOnOrAfter() + ", server_date: " + serverDate + ")"); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "Token date expired (getNotOnOrAfter = " + assertion.getConditions().getNotOnOrAfter() + " ), server_date: " + serverDate); } } private void verifyMethodBearer(String userIP, Assertion assertion) throws EIDASSAMLEngineException { LOG.trace("Verified method Bearer"); for (final Iterator<SubjectConfirmation> iter = assertion.getSubject().getSubjectConfirmations() .iterator(); iter.hasNext();) { final SubjectConfirmation element = iter.next(); final boolean isBearer = SubjectConfirmation.METHOD_BEARER.equals(element.getMethod()); final boolean ipValidate = super.getSamlCoreProperties().isIpValidation(); if (ipValidate) { if (isBearer) { if (StringUtils.isBlank(userIP)) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : browser_ip is null or empty."); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "browser_ip is null or empty."); } else if (StringUtils.isBlank(element.getSubjectConfirmationData().getAddress())) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : token_ip attribute is null or empty."); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "token_ip attribute is null or empty."); } } final boolean ipEqual = element.getSubjectConfirmationData().getAddress().equals(userIP); // Validation ipUser if (!ipEqual && ipValidate) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : SubjectConfirmation BEARER: IPs doesn't match : token_ip [{}] browser_ip [{}]", element.getSubjectConfirmationData().getAddress(), userIP); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "IPs doesn't match : token_ip (" + element.getSubjectConfirmationData().getAddress() + ") browser_ip (" + userIP + ")"); } } } } private void validateAssertionsSignature(Response samlObject) throws EIDASSAMLEngineException { try { for (Assertion a : samlObject.getAssertions()) { if (a.isSigned()) { super.validateSignature(a, getExtensionProcessor().getFormat().getName()); } } } catch (SAMLEngineException e) { EIDASSAMLEngineException exc = new EIDASSAMLEngineException( EIDASErrors.INVALID_ASSERTION_SIGNATURE.errorCode(), EIDASErrors.INVALID_ASSERTION_SIGNATURE.errorMessage(), e); throw exc; } } private SignableSAMLObject validateEidasSamlSignature(SignableSAMLObject samlObject, String messageFormat) throws EIDASSAMLEngineException { boolean validateSign = true; LOG.debug(SAML_EXCHANGE, super.getSamlCoreProperties().getProperty("validateSignature")); if (StringUtils.isNotBlank(super.getSamlCoreProperties().getProperty("validateSignature"))) { validateSign = Boolean.valueOf(super.getSamlCoreProperties().getProperty("validateSignature")); } SignableSAMLObject validSamlObject = samlObject; if (validateSign) { LOG.trace("Validate Signature."); try { if ((samlObject instanceof Response || samlObject instanceof AuthnRequest) && validSamlObject.getSignature() == null) { throw new SAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "Invalid signature"); } if (samlObject instanceof Response) { setCountryResponseFrom(getCountry(samlObject.getSignature().getKeyInfo())); LOG.debug(SAML_EXCHANGE, "Response received from country: " + getCountryResponseFrom()); } validSamlObject = (SignableSAMLObject) super.validateSignature(samlObject, messageFormat); if (samlObject instanceof Response) { //check assertions signature, if any validateAssertionsSignature((Response) samlObject); } } catch (SAMLEngineException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : SAMLEngineException validateSignature.", e.getMessage()); LOG.debug(SAML_EXCHANGE, "SAMLEngineException validateSignature.", e); EIDASSAMLEngineException exc = new EIDASSAMLEngineException(e); if (EIDASErrors.isErrorCode(e.getMessage())) { exc.setErrorCode(e.getMessage()); } if (EIDASErrors.isErrorCode(e.getErrorCode())) { exc.setErrorCode(e.getErrorCode()); } throw exc; } } return validSamlObject; } /** * Validate SAML. * * @param tokenSaml the token SAML * * @return the signable SAML object * * @throws EIDASSAMLEngineException the EIDASSAML engine exception */ private SignableSAMLObject validateEidasSaml(final byte[] tokenSaml, String messageFormat) throws EIDASSAMLEngineException { LOG.trace("Validate saml message."); if (tokenSaml == null) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Saml authentication request is null."); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), "Saml authentication request is null."); } LOG.trace("Generate AuthnRequest from request."); SignableSAMLObject samlObject; try { samlObject = (SignableSAMLObject) super.unmarshall(tokenSaml); } catch (SAMLEngineException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : SAMLEngineException unmarshall.", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : SAMLEngineException unmarshall.", e); throw new EIDASSAMLEngineException(EIDASErrors.INVALID_ENCRYPTION_ALGORITHM.errorCode(), EIDASErrors.INVALID_ENCRYPTION_ALGORITHM.errorMessage(), e); } samlObject = validateEidasSamlSignature(samlObject, messageFormat); LOG.trace("Validate Schema."); final ValidatorSuite validatorSuite = Configuration.getValidatorSuite("saml2-core-schema-validator"); try { validatorSuite.validate(samlObject); } catch (ValidationException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : ValidationException.", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : ValidationException.", e); throw new EIDASSAMLEngineException(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode(), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorMessage(), e); } return samlObject; } /** * @deprecated */ @Deprecated public EIDASAuthnRequest generateEIDASAuthnRequestWithoutValidation(final EIDASAuthnRequest request) throws EIDASSAMLEngineException { LOG.trace("Generate SAMLAuthnRequest."); // Validate Parameters mandatories selectFormat(request.getMessageFormatName()); final AuthnRequest authnRequestAux = SAMLEngineUtils.generateSAMLAuthnRequest( SAMLEngineUtils.generateNCName(), SAMLVersion.VERSION_20, SAMLEngineUtils.getCurrentTime()); // Set name spaces. setRequestNameSpaces(authnRequestAux); // Add parameter Mandatory authnRequestAux.setForceAuthn(Boolean.TRUE); // Add parameter Mandatory authnRequestAux.setIsPassive(Boolean.FALSE); authnRequestAux.setAssertionConsumerServiceURL(request.getAssertionConsumerServiceURL()); authnRequestAux.setProviderName(request.getProviderName()); // Add protocol binding if (SAMLEngineUtils.isEidasFormat(request)) { authnRequestAux.setProtocolBinding(null); } else { authnRequestAux.setProtocolBinding( request.getBinding() == null ? null : getProtocolBinding(request.getBinding())); } // Add parameter optional // Destination is mandatory // The application must to know the destination if (StringUtils.isNotBlank(request.getDestination())) { authnRequestAux.setDestination(request.getDestination()); } // Consent is optional. Set from SAMLEngine.xml - consent. authnRequestAux.setConsent(super.getSamlCoreProperties().getConsentAuthnRequest()); final Issuer issuer = SAMLEngineUtils.generateIssuer(); if (request.getIssuer() != null) { issuer.setValue(SAMLEngineUtils.getValidIssuerValue(request.getIssuer())); } else { issuer.setValue(super.getSamlCoreProperties().getRequester()); } // Optional final String formatEntity = super.getSamlCoreProperties().getFormatEntity(); if (StringUtils.isNotBlank(formatEntity)) { issuer.setFormat(formatEntity); } authnRequestAux.setIssuer(issuer); addAuthnContext(request, authnRequestAux); // Generate format extensions. final Extensions formatExtensions = getExtensionProcessor().generateExtensions(this, request); // add the extensions to the SAMLAuthnRequest authnRequestAux.setExtensions(formatExtensions); addNameIDPolicy(authnRequestAux, request.getEidasNameidFormat()); // the result contains an authentication request token (byte[]), // identifier of the token, and all parameters from the request. final EIDASAuthnRequest authRequest = getExtensionProcessor() .processExtensions(authnRequestAux.getExtensions()); try { authRequest.setTokenSaml( super.signAndMarshall(authnRequestAux, getExtensionProcessor().getFormat().getName())); } catch (SAMLEngineException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Sign and Marshall.", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : Sign and Marshall.", e); throw new EIDASSAMLEngineException(EIDASUtil.getConfig(EIDASErrors.INTERNAL_ERROR.errorCode()), EIDASUtil.getConfig(EIDASErrors.INTERNAL_ERROR.errorMessage()), e); } authRequest.setSamlId(authnRequestAux.getID()); authRequest.setDestination(authnRequestAux.getDestination()); authRequest.setAssertionConsumerServiceURL(authnRequestAux.getAssertionConsumerServiceURL()); authRequest.setProviderName(authnRequestAux.getProviderName()); authRequest.setIssuer(authnRequestAux.getIssuer().getValue()); return authRequest; } public EIDASAuthnRequest generateEIDASAuthnRequestWithoutSign(final EIDASAuthnRequest request) throws EIDASSAMLEngineException { LOG.trace("Generate SAMLAuthnRequest."); // Validate Parameters mandatories final AuthnRequest authnRequestAux = SAMLEngineUtils.generateSAMLAuthnRequest( SAMLEngineUtils.generateNCName(), SAMLVersion.VERSION_20, SAMLEngineUtils.getCurrentTime()); // Set name spaces. setRequestNameSpaces(authnRequestAux); // Add parameter Mandatory authnRequestAux.setForceAuthn(Boolean.TRUE); // Add parameter Mandatory authnRequestAux.setIsPassive(Boolean.FALSE); authnRequestAux.setAssertionConsumerServiceURL(request.getAssertionConsumerServiceURL()); authnRequestAux.setProviderName(request.getProviderName()); // Add protocol binding if (SAMLEngineUtils.isEidasFormat(request)) { authnRequestAux.setProtocolBinding(null); } else { authnRequestAux.setProtocolBinding( request.getBinding() == null ? null : getProtocolBinding(request.getBinding())); } // Add parameter optional // Destination is mandatory // The application must to know the destination if (StringUtils.isNotBlank(request.getDestination())) { authnRequestAux.setDestination(request.getDestination()); } // Consent is optional. Set from SAMLEngine.xml - consent. authnRequestAux.setConsent(super.getSamlCoreProperties().getConsentAuthnRequest()); final Issuer issuer = SAMLEngineUtils.generateIssuer(); if (request.getIssuer() != null) { issuer.setValue(SAMLEngineUtils.getValidIssuerValue(request.getIssuer())); } else { issuer.setValue(super.getSamlCoreProperties().getRequester()); } // Optional final String formatEntity = super.getSamlCoreProperties().getFormatEntity(); if (StringUtils.isNotBlank(formatEntity)) { issuer.setFormat(formatEntity); } authnRequestAux.setIssuer(issuer); addAuthnContext(request, authnRequestAux); // Generate format extensions. final Extensions formatExtensions = getExtensionProcessor().generateExtensions(this, request); // add the extensions to the SAMLAuthnRequest authnRequestAux.setExtensions(formatExtensions); addNameIDPolicy(authnRequestAux, request.getEidasNameidFormat()); // the result contains an authentication request token (byte[]), // identifier of the token, and all parameters from the request. final EIDASAuthnRequest authRequest = getExtensionProcessor() .processExtensions(authnRequestAux.getExtensions()); try { authRequest.setTokenSaml(super.noSignAndMarshall(authnRequestAux)); } catch (SAMLEngineException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Sign and Marshall.", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : Sign and Marshall.", e); throw new EIDASSAMLEngineException(EIDASErrors.INTERNAL_ERROR.errorCode(), EIDASErrors.INTERNAL_ERROR.errorMessage(), e); } authRequest.setSamlId(authnRequestAux.getID()); authRequest.setDestination(authnRequestAux.getDestination()); authRequest.setAssertionConsumerServiceURL(authnRequestAux.getAssertionConsumerServiceURL()); authRequest.setProviderName(authnRequestAux.getProviderName()); authRequest.setIssuer(authnRequestAux.getIssuer().getValue()); return authRequest; } public static String validateSchema(String samlRequestXML) throws EIDASSAMLEngineException { Document document; javax.xml.validation.Schema schema = null; javax.xml.validation.Validator validator; try { BasicParserPool ppMgr = getNewBasicSecuredParserPool(); ppMgr.setNamespaceAware(true); InputStream inputStream = new ByteArrayInputStream(samlRequestXML.getBytes("UTF-8")); document = ppMgr.parse(inputStream); Element samlElemnt = document.getDocumentElement(); schema = SAMLSchemaBuilder.getSAML11Schema(); validator = schema.newValidator(); DOMSource domSrc = new DOMSource(samlElemnt); validator.validate(domSrc); } catch (XMLParserException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Validate schema exception", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : Validate schema exception", e); if (e.getCause().toString().contains("DOCTYPE is disallowed")) { throw new EIDASSAMLEngineException( EIDASUtil.getConfig(EIDASErrors.DOC_TYPE_NOT_ALLOWED.errorCode()), EIDASErrors.DOC_TYPE_NOT_ALLOWED.errorCode(), "SAML request contains a DOCTYPE which is not allowed for security reason"); } else { throw new EIDASSAMLEngineException( EIDASUtil.getConfig(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode()), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorMessage(), e); } } catch (SAXException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Validate schema exception", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : Validate schema exception", e); throw new EIDASSAMLEngineException( EIDASUtil.getConfig(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode()), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorMessage(), e); } catch (IOException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Validate schema exception", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : Validate schema exception", e); throw new EIDASSAMLEngineException( EIDASUtil.getConfig(EIDASErrors.MESSAGE_VALIDATION_ERROR.errorCode()), EIDASErrors.MESSAGE_VALIDATION_ERROR.errorMessage(), e); } return samlRequestXML; } /** * Resign authentication request ( for validation purpose). * @return the resigned request * @throws EIDASSAMLEngineException the EIDASSAML engine exception */ public EIDASAuthnRequest resignEIDASAuthnRequest(final EIDASAuthnRequest request, boolean changeProtocol) throws EIDASSAMLEngineException { LOG.trace("Generate SAMLAuthnRequest."); EIDASAuthnRequest authRequest = null; AuthnRequest authnRequestAux = null; try { authRequest = (EIDASAuthnRequest) request.clone(); } catch (CloneNotSupportedException e) { LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : Clone not supported in resignEIDASAuthnRequest {}", e); LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : Clone not supported in resignEIDASAuthnRequest {}", e.getMessage()); } byte[] tokenSaml = request.getTokenSaml(); try { authnRequestAux = (AuthnRequest) unmarshall(tokenSaml); if (SAMLEngineUtils.isEidasFormat(request)) { authnRequestAux.setProtocolBinding(null); } else if (authnRequestAux.getProtocolBinding() == null || changeProtocol) { authnRequestAux.setProtocolBinding(getProtocolBinding(authRequest.getBinding())); } } catch (SAMLEngineException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : resignEIDASAuthnRequest {}", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : resignEIDASAuthnRequest {}", e); } try { authRequest.setTokenSaml( super.signAndMarshall(authnRequestAux, getExtensionProcessor().getFormat().getName())); } catch (SAMLEngineException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : resignEIDASAuthnRequest : Sign and Marshall.{}", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : resignEIDASAuthnRequest : Sign and Marshall.{}", e); throw new EIDASSAMLEngineException(EIDASErrors.INTERNAL_ERROR.errorCode(), EIDASErrors.INTERNAL_ERROR.errorMessage(), e); } return authRequest; } public EIDASAuthnRequest resignEIDASAuthnRequest(final EIDASAuthnRequest request) throws EIDASSAMLEngineException { return resignEIDASAuthnRequest(request, false); } /** * Resign tokenSaml ( for validation purpose). * @return the resigned request * @throws EIDASSAMLEngineException the EIDASSAML engine exception */ public byte[] resignEIDASTokenSAML(final byte[] tokenSaml) throws EIDASSAMLEngineException { LOG.trace("Generate SAMLAuthnRequest."); AuthnRequest authnRequestAux = null; try { authnRequestAux = (AuthnRequest) unmarshall(tokenSaml); releaseExtensionsDom(authnRequestAux); } catch (SAMLEngineException e) { LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : unmarshall {}", e); LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : unmarshall {}", e.getMessage()); } if (authnRequestAux == null) { throw new EIDASSAMLEngineException(EIDASErrors.INTERNAL_ERROR.errorCode(), EIDASErrors.INTERNAL_ERROR.errorCode(), "invalid AuthnRequest"); } try { return super.signAndMarshall(authnRequestAux, getExtensionProcessor().getFormat().getName()); } catch (SAMLEngineException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : resignEIDASTokenSAML : Sign and Marshall.", e); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : resignEIDASTokenSAML : Sign and Marshall.", e.getMessage()); throw new EIDASSAMLEngineException(EIDASErrors.INTERNAL_ERROR.errorCode(), EIDASErrors.INTERNAL_ERROR.errorMessage(), e); } } private void releaseExtensionsDom(AuthnRequest authnRequestAux) { if (authnRequestAux.getExtensions() == null) { return; } authnRequestAux.getExtensions().releaseDOM(); authnRequestAux.getExtensions().releaseChildrenDOM(true); } /** * Resigns the saml token checking previously if it is encrypted * @param tokenSaml * @return * @throws EIDASSAMLEngineException */ public byte[] checkAndResignEIDASTokenSAML(final byte[] tokenSaml) throws EIDASSAMLEngineException { SignableSAMLObject samlObject = null; try { samlObject = (SignableSAMLObject) unmarshall(tokenSaml); samlObject = validateEidasSamlSignature(samlObject, getExtensionProcessor().getFormat().getName()); } catch (SAMLEngineException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : unmarshall {}", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : unmarshall {}", e); } if (samlObject == null) { throw new EIDASSAMLEngineException(EIDASErrors.INTERNAL_ERROR.errorCode(), EIDASErrors.INTERNAL_ERROR.errorMessage(), "BUSINESS EXCEPTION : invalid AuthnRequest"); } try { return super.signAndMarshall(samlObject, getExtensionProcessor().getFormat().getName()); } catch (SAMLEngineException e) { LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : checkAndResignEIDASTokenSAML : Sign and Marshall.", e); LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : checkAndResignEIDASTokenSAML : Sign and Marshall.", e.getMessage()); throw new EIDASSAMLEngineException(EIDASErrors.INTERNAL_ERROR.errorCode(), EIDASErrors.INTERNAL_ERROR.errorMessage(), e); } } /** * Returns true when the input contains an encrypted SAML Response * @param tokenSaml * @return * @throws EIDASSAMLEngineException */ public boolean isEncryptedSamlResponse(final byte[] tokenSaml) throws EIDASSAMLEngineException { SignableSAMLObject samlObject = null; try { samlObject = (SignableSAMLObject) unmarshall(tokenSaml); } catch (SAMLEngineException e) { LOG.info(SAML_EXCHANGE, "BUSINESS EXCEPTION : unmarshall {}", e.getMessage()); LOG.debug(SAML_EXCHANGE, "BUSINESS EXCEPTION : unmarshall {}", e); } if (samlObject instanceof Response) { Response response = (Response) samlObject; return response.getEncryptedAssertions() != null && !response.getEncryptedAssertions().isEmpty(); } return false; } /** * computes SAML binding from http binding * @param binding * @return */ private String getProtocolBinding(String binding) { if (EIDASAuthnRequest.BINDING_REDIRECT.equalsIgnoreCase(binding)) { return SAMLConstants.SAML2_REDIRECT_BINDING_URI; } else if (EIDASAuthnRequest.BINDING_POST.equalsIgnoreCase(binding)) { return SAMLConstants.SAML2_POST_BINDING_URI; } else if (EIDASAuthnRequest.BINDING_EMPTY.equalsIgnoreCase(binding)) { return null; } return super.getSamlCoreProperties().getProtocolBinding(); } ExtensionProcessorI extensionProcessor; public ExtensionProcessorI getExtensionProcessor() { if (extensionProcessor == null) { setExtensionProcessor(new EidasExtensionProcessor()); } return extensionProcessor; } public void setExtensionProcessor(ExtensionProcessorI extensionProcessor) { this.extensionProcessor = extensionProcessor; if (extensionProcessor != null) { this.extensionProcessor.configureExtension(); } } ExtensionProcessorI availableExtensionProcessors[] = new ExtensionProcessorI[] { new EidasExtensionProcessor(), new StorkExtensionProcessor() }; public ExtensionProcessorI[] getExtensionProcessors() { // ExtensionProcessorI returnedExtensionProcessors[]=new ExtensionProcessorI[2]; // System.arraycopy(availableExtensionProcessors, 0, returnedExtensionProcessors, 0,availableExtensionProcessors.length); List<ExtensionProcessorI> processors = new ArrayList<ExtensionProcessorI>(); Set<String> formatNames = super.getSamlCoreProperties().getSupportedMessageFormatNames(); for (ExtensionProcessorI processor : availableExtensionProcessors) { if (formatNames.contains(processor.getFormat().getName())) { processors.add(processor); } } return processors.toArray(new ExtensionProcessorI[] {}); } /** * init supported format from the requested attributes * Implementation note: currently, the set of supported attributes names for each format should be disjunct * @param attlist */ public void initRequestedAttributes(Iterable<PersonalAttribute> attlist) { Set<String>[] supportedAttrSets = new Set[] { new HashSet<String>(EIDASAttributes.ATTRIBUTES_TO_SHORTNAMES.values()), new HashSet<String>(STORKAttributes.ATTRIBUTES_SET_NAMES.values()) }; ExtensionProcessorI[] extensionProcessors = { new EidasExtensionProcessor(), new StorkExtensionProcessor() }; for (PersonalAttribute att : attlist) { for (int i = 0; i < supportedAttrSets.length; i++) { Set<String> set = supportedAttrSets[i]; if (set.contains(att.getName())) { setExtensionProcessor(extensionProcessors[i]); return; } } } } private boolean tryProcessExtensions(ExtensionProcessorI extensionProcessor, AuthnRequest samlRequest) throws ValidationException { try { EIDASAuthnRequest request = extensionProcessor.processExtensions(samlRequest.getExtensions()); //format discriminator goes here if (request != null) { return extensionProcessor.isValidRequest(samlRequest); } } catch (EIDASSAMLEngineException e) { throw new ValidationException(e); } return false; } }