From bee5dd259a4438d45ecd1bcc26dfba12875236d6 Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Tue, 26 Jun 2018 11:03:48 +0200 Subject: initial commit --- .../idp/impl/builder/PVP2AssertionBuilder.java | 448 +++++++++++++++++++++ 1 file changed, 448 insertions(+) create mode 100644 eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/builder/PVP2AssertionBuilder.java (limited to 'eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/builder/PVP2AssertionBuilder.java') diff --git a/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/builder/PVP2AssertionBuilder.java b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/builder/PVP2AssertionBuilder.java new file mode 100644 index 00000000..7369da15 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_idp/src/main/java/at/gv/egiz/eaaf/modules/pvp2/idp/impl/builder/PVP2AssertionBuilder.java @@ -0,0 +1,448 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.modules.pvp2.idp.impl.builder; + +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.core.Assertion; +import org.opensaml.saml2.core.Attribute; +import org.opensaml.saml2.core.AttributeQuery; +import org.opensaml.saml2.core.AttributeStatement; +import org.opensaml.saml2.core.Audience; +import org.opensaml.saml2.core.AudienceRestriction; +import org.opensaml.saml2.core.AuthnContext; +import org.opensaml.saml2.core.AuthnContextClassRef; +import org.opensaml.saml2.core.AuthnRequest; +import org.opensaml.saml2.core.AuthnStatement; +import org.opensaml.saml2.core.Conditions; +import org.opensaml.saml2.core.Issuer; +import org.opensaml.saml2.core.NameID; +import org.opensaml.saml2.core.RequestedAuthnContext; +import org.opensaml.saml2.core.Subject; +import org.opensaml.saml2.core.SubjectConfirmation; +import org.opensaml.saml2.core.SubjectConfirmationData; +import org.opensaml.saml2.core.impl.AuthnRequestImpl; +import org.opensaml.saml2.metadata.AssertionConsumerService; +import org.opensaml.saml2.metadata.AttributeConsumingService; +import org.opensaml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml2.metadata.NameIDFormat; +import org.opensaml.saml2.metadata.RequestedAttribute; +import org.opensaml.saml2.metadata.SPSSODescriptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.Base64Utils; + +import at.gv.egiz.eaaf.core.api.data.EAAFConstants; +import at.gv.egiz.eaaf.core.api.data.ILoALevelMapper; +import at.gv.egiz.eaaf.core.api.idp.IAuthData; +import at.gv.egiz.eaaf.core.api.idp.ISPConfiguration; +import at.gv.egiz.eaaf.core.api.idp.slo.SLOInformationInterface; +import at.gv.egiz.eaaf.core.exceptions.UnavailableAttributeException; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl; +import at.gv.egiz.eaaf.core.impl.utils.Random; +import at.gv.egiz.eaaf.modules.pvp2.PVPConstants; +import at.gv.egiz.eaaf.modules.pvp2.exception.PVP2Exception; +import at.gv.egiz.eaaf.modules.pvp2.exception.QAANotSupportedException; +import at.gv.egiz.eaaf.modules.pvp2.idp.api.builder.ISubjectNameIdGenerator; +import at.gv.egiz.eaaf.modules.pvp2.idp.exception.ResponderErrorException; +import at.gv.egiz.eaaf.modules.pvp2.idp.exception.UnprovideableAttributeException; +import at.gv.egiz.eaaf.modules.pvp2.idp.impl.PVPSProfilePendingRequest; +import at.gv.egiz.eaaf.modules.pvp2.impl.builder.PVPAttributeBuilder; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.QAALevelVerifier; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.SAML2Utils; + +@Service("PVP2AssertionBuilder") +public class PVP2AssertionBuilder implements PVPConstants { + + private static final Logger log = LoggerFactory.getLogger(PVP2AssertionBuilder.class); + @Autowired private ILoALevelMapper loaLevelMapper; + @Autowired private ISubjectNameIdGenerator subjectNameIdGenerator; + + + /** + * Build a PVP assertion as response for a SAML2 AttributeQuery request + * + * @param issuerEntityID EnitiyID, which should be used for this IDP response + * @param attrQuery AttributeQuery request from Service-Provider + * @param attrList List of PVP response attributes + * @param now Current time + * @param validTo ValidTo time of the assertion + * @param qaaLevel QAA level of the authentication + * @param sessionIndex SAML2 SessionIndex, which should be included * + * @return PVP 2.1 Assertion + * @throws PVP2Exception + */ + public Assertion buildAssertion(String issuerEntityID, AttributeQuery attrQuery, + List attrList, DateTime now, DateTime validTo, String qaaLevel, String sessionIndex) throws PVP2Exception { + + AuthnContextClassRef authnContextClassRef = SAML2Utils.createSAMLObject(AuthnContextClassRef.class); + authnContextClassRef.setAuthnContextClassRef(qaaLevel); + + NameID subjectNameID = SAML2Utils.createSAMLObject(NameID.class); + subjectNameID.setFormat(attrQuery.getSubject().getNameID().getFormat()); + subjectNameID.setValue(attrQuery.getSubject().getNameID().getValue()); + + SubjectConfirmationData subjectConfirmationData = null; + + return buildGenericAssertion(issuerEntityID, attrQuery.getIssuer().getValue(), now, + authnContextClassRef, attrList, subjectNameID, subjectConfirmationData, sessionIndex, + validTo); + } + + + /** + * Build a PVP 2.1 assertion as response of a SAML2 AuthnRequest + * + * @param issuerEntityID EnitiyID, which should be used for this IDP response + * @param pendingReq Current processed pendingRequest DAO + * @param authnRequest Current processed PVP AuthnRequest + * @param authData AuthenticationData of the user, which is already authenticated + * @param peerEntity SAML2 EntityDescriptor of the service-provider, which receives the response + * @param date TimeStamp + * @param assertionConsumerService SAML2 endpoint of the service-provider, which should be used + * @param sloInformation Single LogOut information DAO + * @return + * @throws PVP2Exception + */ + public Assertion buildAssertion(String issuerEntityID, PVPSProfilePendingRequest pendingReq, AuthnRequest authnRequest, + IAuthData authData, EntityDescriptor peerEntity, DateTime date, + AssertionConsumerService assertionConsumerService, SLOInformationInterface sloInformation) + throws PVP2Exception { + + RequestedAuthnContext reqAuthnContext = authnRequest + .getRequestedAuthnContext(); + + AuthnContextClassRef authnContextClassRef = SAML2Utils + .createSAMLObject(AuthnContextClassRef.class); + + ISPConfiguration oaParam = pendingReq.getServiceProviderConfiguration(); + + if (reqAuthnContext == null) { + authnContextClassRef.setAuthnContextClassRef(authData.getEIDASQAALevel()); + + } else { + + boolean eIDAS_qaa_found = false; + + List reqAuthnContextClassRefIt = reqAuthnContext + .getAuthnContextClassRefs(); + + if (reqAuthnContextClassRefIt.size() == 0) { + QAALevelVerifier.verifyQAALevel(authData.getEIDASQAALevel(), EAAFConstants.EIDAS_QAA_HIGH); + + eIDAS_qaa_found = true; + authnContextClassRef.setAuthnContextClassRef(EAAFConstants.EIDAS_QAA_HIGH); + + } else { + for (AuthnContextClassRef authnClassRef : reqAuthnContextClassRefIt) { + String qaa_uri = authnClassRef.getAuthnContextClassRef(); + + if (!qaa_uri.trim().startsWith(EAAFConstants.EIDAS_QAA_PREFIX)) { + if (loaLevelMapper != null) { + log.debug("Find no eIDAS LoA. Start mapping process ... " ); + qaa_uri = loaLevelMapper.mapToeIDASLoA(qaa_uri.trim()); + + } else + log.debug("AuthnRequest contains no eIDAS LoA. NO LoA mapper FOUND, ignore " + + "'" + qaa_uri.trim() + "'"); + + } + + if (qaa_uri.trim().equals(EAAFConstants.EIDAS_QAA_HIGH) + || qaa_uri.trim().equals(EAAFConstants.EIDAS_QAA_SUBSTANTIAL) + || qaa_uri.trim().equals(EAAFConstants.EIDAS_QAA_LOW)) { + + if (authData.isForeigner()) { + QAALevelVerifier.verifyQAALevel(authData.getEIDASQAALevel(), oaParam.getMinimumLevelOfAssurence()); + + eIDAS_qaa_found = true; + authnContextClassRef.setAuthnContextClassRef(authData.getEIDASQAALevel()); + + } else { + + QAALevelVerifier.verifyQAALevel(authData.getEIDASQAALevel(), + qaa_uri.trim()); + + eIDAS_qaa_found = true; + authnContextClassRef.setAuthnContextClassRef(authData.getEIDASQAALevel()); + + } + break; + } + } + } + + if (!eIDAS_qaa_found) + throw new QAANotSupportedException(EAAFConstants.EIDAS_QAA_HIGH); + + } + + + + SPSSODescriptor spSSODescriptor = peerEntity + .getSPSSODescriptor(SAMLConstants.SAML20P_NS); + + //add Attributes to Assertion + List attrList = new ArrayList(); + if (spSSODescriptor.getAttributeConsumingServices() != null && + spSSODescriptor.getAttributeConsumingServices().size() > 0) { + + Integer aIdx = authnRequest.getAttributeConsumingServiceIndex(); + int idx = 0; + + AttributeConsumingService attributeConsumingService = null; + if (aIdx != null) { + idx = aIdx.intValue(); + attributeConsumingService = spSSODescriptor + .getAttributeConsumingServices().get(idx); + + } else { + List attrConsumingServiceList = spSSODescriptor.getAttributeConsumingServices(); + for (AttributeConsumingService el : attrConsumingServiceList) { + if (el.isDefault()) + attributeConsumingService = el; + } + } + + /* + * TODO: maybe use first AttributeConsumingService if no is selected + * in request or on service is marked as default + * + */ + if (attributeConsumingService == null ) { + List attrConsumingServiceList = spSSODescriptor.getAttributeConsumingServices(); + if (attrConsumingServiceList != null && !attrConsumingServiceList.isEmpty()) + attributeConsumingService = attrConsumingServiceList.get(0); + + } + + + if (attributeConsumingService != null) { + Iterator it = attributeConsumingService + .getRequestAttributes().iterator(); + while (it.hasNext()) { + RequestedAttribute reqAttribut = it.next(); + try { + Attribute attr = PVPAttributeBuilder.buildAttribute( + reqAttribut.getName(), oaParam, authData); + if (attr == null) { + if (reqAttribut.isRequired()) { + throw new UnprovideableAttributeException( + reqAttribut.getName()); + } + } else { + attrList.add(attr); + } + + } catch (UnavailableAttributeException e) { + log.info( + "Attribute generation for " + + reqAttribut.getFriendlyName() + " not possible."); + if (reqAttribut.isRequired()) { + throw new UnprovideableAttributeException( + reqAttribut.getName()); + } + + + } catch (PVP2Exception e) { + log.info( + "Attribute generation failed! for " + + reqAttribut.getFriendlyName()); + if (reqAttribut.isRequired()) { + throw new UnprovideableAttributeException( + reqAttribut.getName()); + } + + } catch (Exception e) { + log.warn( + "General Attribute generation failed! for " + + reqAttribut.getFriendlyName(), e); + if (reqAttribut.isRequired()) { + throw new UnprovideableAttributeException( + reqAttribut.getName()); + } + + } + } + } + } + + //generate subjectNameId + NameID subjectNameID = SAML2Utils.createSAMLObject(NameID.class); + Pair subjectNameIdPair = subjectNameIdGenerator.generateSubjectNameId(authData, oaParam); + subjectNameID.setValue(subjectNameIdPair.getFirst()); + subjectNameID.setNameQualifier(subjectNameIdPair.getSecond()); + + //get NameIDFormat from request + String nameIDFormat = NameID.TRANSIENT; + AuthnRequest authnReq = (AuthnRequestImpl) authnRequest; + if (authnReq.getNameIDPolicy() != null && + StringUtils.isNotEmpty(authnReq.getNameIDPolicy().getFormat())) { + nameIDFormat = authnReq.getNameIDPolicy().getFormat(); + + } else { + //get NameIDFormat from metadata + List metadataNameIDFormats = spSSODescriptor.getNameIDFormats(); + + if (metadataNameIDFormats != null) { + + for (NameIDFormat el : metadataNameIDFormats) { + if (NameID.PERSISTENT.equals(el.getFormat())) { + nameIDFormat = NameID.PERSISTENT; + break; + + } else if (NameID.TRANSIENT.equals(el.getFormat()) || + NameID.UNSPECIFIED.equals(el.getFormat())) + break; + + } + } + } + + if (NameID.TRANSIENT.equals(nameIDFormat) || NameID.UNSPECIFIED.equals(nameIDFormat)) { + String random = Random.nextHexRandom32(); + String nameID = subjectNameID.getValue(); + + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] hash = md.digest((nameID + random).getBytes("ISO-8859-1")); + subjectNameID.setValue(Base64Utils.encodeToString(hash)); + subjectNameID.setNameQualifier(null); + subjectNameID.setFormat(NameID.TRANSIENT); + + } catch (Exception e) { + log.warn("PVP2 subjectNameID error", e); + throw new ResponderErrorException("pvp2.13", null, e); + } + + } else + subjectNameID.setFormat(nameIDFormat); + + + String sessionIndex = null; + + //if request is a reauthentication and NameIDFormat match reuse old session information + if (StringUtils.isNotEmpty(authData.getNameID()) && + StringUtils.isNotEmpty(authData.getNameIDFormat()) && + nameIDFormat.equals(authData.getNameIDFormat())) { + subjectNameID.setValue(authData.getNameID()); + sessionIndex = authData.getSessionIndex(); + + } + + // + if (StringUtils.isEmpty(sessionIndex)) + sessionIndex = SAML2Utils.getSecureIdentifier(); + + SubjectConfirmationData subjectConfirmationData = SAML2Utils + .createSAMLObject(SubjectConfirmationData.class); + subjectConfirmationData.setInResponseTo(authnRequest.getID()); + subjectConfirmationData.setNotOnOrAfter(new DateTime(authData.getSsoSessionValidTo().getTime())); +// subjectConfirmationData.setNotBefore(date); + + //set 'recipient' attribute in subjectConformationData + subjectConfirmationData.setRecipient(assertionConsumerService.getLocation()); + + //set IP address of the user machine as 'Address' attribute in subjectConformationData + String usersIPAddress = pendingReq.getGenericData( + RequestImpl.DATAID_REQUESTER_IP_ADDRESS, String.class); + if (StringUtils.isNotEmpty(usersIPAddress)) + subjectConfirmationData.setAddress(usersIPAddress); + + //set SLO information + sloInformation.setUserNameIdentifier(subjectNameID.getValue()); + sloInformation.setNameIDFormat(subjectNameID.getFormat()); + sloInformation.setSessionIndex(sessionIndex); + + return buildGenericAssertion(issuerEntityID, peerEntity.getEntityID(), date, authnContextClassRef, attrList, subjectNameID, subjectConfirmationData, sessionIndex, subjectConfirmationData.getNotOnOrAfter()); + } + + /** + * + * @param issuer IDP EntityID + * @param entityID Service Provider EntityID + * @param date + * @param authnContextClassRef + * @param attrList + * @param subjectNameID + * @param subjectConfirmationData + * @param sessionIndex + * @param isValidTo + * @return + * @throws ConfigurationException + */ + + public Assertion buildGenericAssertion(String issuer, String entityID, DateTime date, + AuthnContextClassRef authnContextClassRef, List attrList, + NameID subjectNameID, SubjectConfirmationData subjectConfirmationData, + String sessionIndex, DateTime isValidTo) throws ResponderErrorException { + Assertion assertion = SAML2Utils.createSAMLObject(Assertion.class); + + AuthnContext authnContext = SAML2Utils + .createSAMLObject(AuthnContext.class); + authnContext.setAuthnContextClassRef(authnContextClassRef); + + AuthnStatement authnStatement = SAML2Utils + .createSAMLObject(AuthnStatement.class); + + authnStatement.setAuthnInstant(date); + authnStatement.setSessionIndex(sessionIndex); + authnStatement.setAuthnContext(authnContext); + + assertion.getAuthnStatements().add(authnStatement); + + AttributeStatement attributeStatement = SAML2Utils + .createSAMLObject(AttributeStatement.class); + attributeStatement.getAttributes().addAll(attrList); + if (attributeStatement.getAttributes().size() > 0) { + assertion.getAttributeStatements().add(attributeStatement); + } + + Subject subject = SAML2Utils.createSAMLObject(Subject.class); + subject.setNameID(subjectNameID); + + SubjectConfirmation subjectConfirmation = SAML2Utils + .createSAMLObject(SubjectConfirmation.class); + subjectConfirmation.setMethod(SubjectConfirmation.METHOD_BEARER); + subjectConfirmation.setSubjectConfirmationData(subjectConfirmationData); + + subject.getSubjectConfirmations().add(subjectConfirmation); + + Conditions conditions = SAML2Utils.createSAMLObject(Conditions.class); + AudienceRestriction audienceRestriction = SAML2Utils + .createSAMLObject(AudienceRestriction.class); + Audience audience = SAML2Utils.createSAMLObject(Audience.class); + + audience.setAudienceURI(entityID); + audienceRestriction.getAudiences().add(audience); + conditions.setNotBefore(date); + conditions.setNotOnOrAfter(isValidTo); + + conditions.getAudienceRestrictions().add(audienceRestriction); + + assertion.setConditions(conditions); + + Issuer issuerObj = SAML2Utils.createSAMLObject(Issuer.class); + + if (issuer.endsWith("/")) + issuer = issuer.substring(0, issuer.length()-1); + issuerObj.setValue(issuer); + issuerObj.setFormat(NameID.ENTITY); + + assertion.setIssuer(issuerObj); + assertion.setSubject(subject); + assertion.setID(SAML2Utils.getSecureIdentifier()); + assertion.setIssueInstant(date); + + return assertion; + } +} -- cgit v1.2.3