/******************************************************************************* * Copyright 2014 Federal Chancellery Austria * MOA-ID has been developed in a cooperation between BRZ, the Federal * Chancellery Austria - ICT staff unit, and Graz University of Technology. * * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by * the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * http://www.osor.eu/eupl/ * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. * * This product combines work with different licenses. See the "NOTICE" text * file for details on the various modules and licenses. * The "NOTICE" text file is part of the distribution. Any derivative works * that you distribute must include a readable copy of the "NOTICE" text file. *******************************************************************************/ package at.gv.egovernment.moa.id.protocols.pvp2x.builder.assertion; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Iterator; import java.util.List; 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.w3c.dom.Element; import at.gv.e_government.reference.namespace.mandates._20040701_.Mandate; import at.gv.e_government.reference.namespace.persondata._20020228_.CorporateBodyType; import at.gv.e_government.reference.namespace.persondata._20020228_.IdentificationType; import at.gv.e_government.reference.namespace.persondata._20020228_.PhysicalPersonType; import at.gv.egovernment.moa.id.auth.builder.BPKBuilder; import at.gv.egovernment.moa.id.commons.api.IOAAuthParameters; import at.gv.egovernment.moa.id.commons.api.exceptions.ConfigurationException; import at.gv.egovernment.moa.id.commons.api.exceptions.MOAIDException; import at.gv.egovernment.moa.id.data.IAuthData; import at.gv.egovernment.moa.id.data.Pair; import at.gv.egovernment.moa.id.data.SLOInformationImpl; import at.gv.egovernment.moa.id.protocols.pvp2x.PVPConstants; import at.gv.egovernment.moa.id.protocols.pvp2x.PVPTargetConfiguration; import at.gv.egovernment.moa.id.protocols.pvp2x.builder.PVPAttributeBuilder; import at.gv.egovernment.moa.id.protocols.pvp2x.builder.attributes.exceptions.UnavailableAttributeException; import at.gv.egovernment.moa.id.protocols.pvp2x.exceptions.NoMandateDataAvailableException; import at.gv.egovernment.moa.id.protocols.pvp2x.exceptions.PVP2Exception; import at.gv.egovernment.moa.id.protocols.pvp2x.exceptions.QAANotSupportedException; import at.gv.egovernment.moa.id.protocols.pvp2x.exceptions.UnprovideableAttributeException; import at.gv.egovernment.moa.id.protocols.pvp2x.utils.SAML2Utils; import at.gv.egovernment.moa.id.util.MandateBuilder; import at.gv.egovernment.moa.id.util.QAALevelVerifier; import at.gv.egovernment.moa.id.util.Random; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.Base64Utils; import at.gv.egovernment.moa.util.Constants; import at.gv.egovernment.moa.util.MiscUtil; public class PVP2AssertionBuilder implements PVPConstants { /** * 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 ConfigurationException */ public static Assertion buildAssertion(String issuerEntityID, AttributeQuery attrQuery, List attrList, DateTime now, DateTime validTo, String qaaLevel, String sessionIndex) throws ConfigurationException { 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 MOAIDException */ public static Assertion buildAssertion(String issuerEntityID, PVPTargetConfiguration pendingReq, AuthnRequest authnRequest, IAuthData authData, EntityDescriptor peerEntity, DateTime date, AssertionConsumerService assertionConsumerService, SLOInformationImpl sloInformation) throws MOAIDException { RequestedAuthnContext reqAuthnContext = authnRequest .getRequestedAuthnContext(); AuthnContextClassRef authnContextClassRef = SAML2Utils .createSAMLObject(AuthnContextClassRef.class); IOAAuthParameters oaParam = pendingReq.getOnlineApplicationConfiguration(); if (reqAuthnContext == null) { authnContextClassRef.setAuthnContextClassRef(authData.getQAALevel()); } else { boolean stork_qaa_1_4_found = false; List reqAuthnContextClassRefIt = reqAuthnContext .getAuthnContextClassRefs(); if (reqAuthnContextClassRefIt.size() == 0) { QAALevelVerifier.verifyQAALevel(authData.getQAALevel(), STORK_QAA_1_4); stork_qaa_1_4_found = true; authnContextClassRef.setAuthnContextClassRef(STORK_QAA_1_4); } else { for (AuthnContextClassRef authnClassRef : reqAuthnContextClassRefIt) { String qaa_uri = authnClassRef.getAuthnContextClassRef(); if (qaa_uri.trim().equals(STORK_QAA_1_4) || qaa_uri.trim().equals(STORK_QAA_1_3) || qaa_uri.trim().equals(STORK_QAA_1_2) || qaa_uri.trim().equals(STORK_QAA_1_1)) { if (authData.isForeigner()) { QAALevelVerifier.verifyQAALevel(authData.getQAALevel(), STORK_QAA_PREFIX + oaParam.getQaaLevel()); stork_qaa_1_4_found = true; authnContextClassRef.setAuthnContextClassRef(authData.getQAALevel()); } else { QAALevelVerifier.verifyQAALevel(authData.getQAALevel(), qaa_uri.trim()); stork_qaa_1_4_found = true; authnContextClassRef.setAuthnContextClassRef(authData.getQAALevel()); } break; } } } if (!stork_qaa_1_4_found) { throw new QAANotSupportedException(STORK_QAA_1_4); } } 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) { Logger.info( "Attribute generation for " + reqAttribut.getFriendlyName() + " not possible."); if (reqAttribut.isRequired()) { throw new UnprovideableAttributeException( reqAttribut.getName()); } } catch (PVP2Exception e) { Logger.info( "Attribute generation failed! for " + reqAttribut.getFriendlyName()); if (reqAttribut.isRequired()) { throw new UnprovideableAttributeException( reqAttribut.getName()); } } catch (Exception e) { Logger.warn( "General Attribute generation failed! for " + reqAttribut.getFriendlyName(), e); if (reqAttribut.isRequired()) { throw new UnprovideableAttributeException( reqAttribut.getName()); } } } } } NameID subjectNameID = SAML2Utils.createSAMLObject(NameID.class); //build nameID and nameID Format from moasession //TODO: nameID generation if (authData.isUseMandate()) { String bpktype = null; String bpk = null; Element mandate = authData.getMandate(); if(mandate != null) { Logger.debug("Read mandator bPK|baseID from full-mandate ... "); Mandate mandateObject = MandateBuilder.buildMandate(mandate); if(mandateObject == null) { throw new NoMandateDataAvailableException(); } CorporateBodyType corporation = mandateObject.getMandator().getCorporateBody(); PhysicalPersonType pysicalperson = mandateObject.getMandator().getPhysicalPerson(); IdentificationType id; if(corporation != null && corporation.getIdentification().size() > 0) id = corporation.getIdentification().get(0); else if (pysicalperson != null && pysicalperson.getIdentification().size() > 0) id = pysicalperson.getIdentification().get(0); else { Logger.error("Failed to generate IdentificationType"); throw new NoMandateDataAvailableException(); } bpktype = id.getType(); bpk = id.getValue().getValue(); } else { Logger.debug("Read mandator bPK|baseID from PVP attributes ... "); bpk = authData.getGenericData(PVPConstants.MANDATE_NAT_PER_SOURCE_PIN_NAME, String.class); bpktype = authData.getGenericData(PVPConstants.MANDATE_NAT_PER_SOURCE_PIN_TYPE_NAME, String.class); if (MiscUtil.isEmpty(bpk)) { //no sourcePin is included --> search for bPK bpk = authData.getGenericData(PVPConstants.MANDATE_NAT_PER_BPK_NAME, String.class); try { if (bpk.contains(":")) bpk = bpk.split(":")[1]; } catch (Exception e) { Logger.warn("Can not split bPK from mandator attribute!", e); } //set bPK-Type from configuration, because it MUST be equal to service-provider type bpktype = oaParam.getAreaSpecificTargetIdentifier(); } else { //sourcePin is include --> check sourcePinType if (MiscUtil.isEmpty(bpktype)) bpktype = Constants.URN_PREFIX_BASEID; } } if (MiscUtil.isEmpty(bpk) || MiscUtil.isEmpty(bpktype)) { throw new NoMandateDataAvailableException(); } if (bpktype.equals(Constants.URN_PREFIX_BASEID)) { Pair calcbPK = new BPKBuilder().generateAreaSpecificPersonIdentifier(bpk, oaParam.getAreaSpecificTargetIdentifier()); subjectNameID.setValue(calcbPK.getFirst()); subjectNameID.setNameQualifier(calcbPK.getSecond()); } else { subjectNameID.setNameQualifier(bpktype); subjectNameID.setValue(bpk); } } else { subjectNameID.setNameQualifier(authData.getBPKType()); subjectNameID.setValue(authData.getBPK()); } String nameIDFormat = NameID.TRANSIENT; //get NameIDFormat from request AuthnRequest authnReq = (AuthnRequestImpl) authnRequest; if (authnReq.getNameIDPolicy() != null && MiscUtil.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.nextRandom(); String nameID = subjectNameID.getValue(); try { MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] hash = md.digest((nameID + random).getBytes("ISO-8859-1")); subjectNameID.setValue(Base64Utils.encode(hash)); subjectNameID.setNameQualifier(null); subjectNameID.setFormat(NameID.TRANSIENT); } catch (Exception e) { Logger.warn("PVP2 subjectNameID error", e); throw new MOAIDException("pvp2.13", null, e); } } else subjectNameID.setFormat(nameIDFormat); String sessionIndex = null; //if request is a reauthentication and NameIDFormat match reuse old session information if (MiscUtil.isNotEmpty(authData.getNameID()) && MiscUtil.isNotEmpty(authData.getNameIDFormat()) && nameIDFormat.equals(authData.getNameIDFormat())) { subjectNameID.setValue(authData.getNameID()); sessionIndex = authData.getSessionIndex(); } // if (MiscUtil.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( PVPTargetConfiguration.DATAID_REQUESTER_IP_ADDRESS, String.class); if (MiscUtil.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 static Assertion buildGenericAssertion(String issuer, String entityID, DateTime date, AuthnContextClassRef authnContextClassRef, List attrList, NameID subjectNameID, SubjectConfirmationData subjectConfirmationData, String sessionIndex, DateTime isValidTo) throws ConfigurationException { 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; } }