View Javadoc

1   /*
2    * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package edu.internet2.middleware.shibboleth.idp.profile.saml2;
18  
19  import java.util.Collection;
20  import java.util.List;
21  import java.util.Map;
22  
23  import org.joda.time.DateTime;
24  import org.opensaml.Configuration;
25  import org.opensaml.common.SAMLObjectBuilder;
26  import org.opensaml.common.SAMLVersion;
27  import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
28  import org.opensaml.common.xml.SAMLConstants;
29  import org.opensaml.saml2.core.Assertion;
30  import org.opensaml.saml2.core.AttributeQuery;
31  import org.opensaml.saml2.core.AttributeStatement;
32  import org.opensaml.saml2.core.Audience;
33  import org.opensaml.saml2.core.AudienceRestriction;
34  import org.opensaml.saml2.core.AuthnRequest;
35  import org.opensaml.saml2.core.Conditions;
36  import org.opensaml.saml2.core.Issuer;
37  import org.opensaml.saml2.core.NameID;
38  import org.opensaml.saml2.core.NameIDPolicy;
39  import org.opensaml.saml2.core.ProxyRestriction;
40  import org.opensaml.saml2.core.Response;
41  import org.opensaml.saml2.core.Statement;
42  import org.opensaml.saml2.core.Status;
43  import org.opensaml.saml2.core.StatusCode;
44  import org.opensaml.saml2.core.StatusMessage;
45  import org.opensaml.saml2.core.StatusResponseType;
46  import org.opensaml.saml2.core.Subject;
47  import org.opensaml.saml2.core.SubjectConfirmation;
48  import org.opensaml.saml2.core.SubjectConfirmationData;
49  import org.opensaml.saml2.encryption.Encrypter;
50  import org.opensaml.saml2.encryption.Encrypter.KeyPlacement;
51  import org.opensaml.saml2.metadata.Endpoint;
52  import org.opensaml.saml2.metadata.SPSSODescriptor;
53  import org.opensaml.security.MetadataCredentialResolver;
54  import org.opensaml.security.MetadataCriteria;
55  import org.opensaml.ws.message.encoder.MessageEncodingException;
56  import org.opensaml.ws.transport.http.HTTPInTransport;
57  import org.opensaml.xml.XMLObjectBuilder;
58  import org.opensaml.xml.encryption.EncryptionException;
59  import org.opensaml.xml.encryption.EncryptionParameters;
60  import org.opensaml.xml.encryption.KeyEncryptionParameters;
61  import org.opensaml.xml.io.Marshaller;
62  import org.opensaml.xml.io.MarshallingException;
63  import org.opensaml.xml.security.CriteriaSet;
64  import org.opensaml.xml.security.SecurityConfiguration;
65  import org.opensaml.xml.security.SecurityException;
66  import org.opensaml.xml.security.SecurityHelper;
67  import org.opensaml.xml.security.credential.Credential;
68  import org.opensaml.xml.security.credential.UsageType;
69  import org.opensaml.xml.security.criteria.EntityIDCriteria;
70  import org.opensaml.xml.security.criteria.UsageCriteria;
71  import org.opensaml.xml.signature.Signature;
72  import org.opensaml.xml.signature.SignatureException;
73  import org.opensaml.xml.signature.Signer;
74  import org.opensaml.xml.util.DatatypeHelper;
75  import org.slf4j.Logger;
76  import org.slf4j.LoggerFactory;
77  
78  import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
79  import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
80  import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncoder;
81  import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncodingException;
82  import edu.internet2.middleware.shibboleth.common.attribute.encoding.SAML2NameIDEncoder;
83  import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML2AttributeAuthority;
84  import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
85  import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
86  import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
87  import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
88  import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.AbstractSAML2ProfileConfiguration;
89  import edu.internet2.middleware.shibboleth.idp.profile.AbstractSAMLProfileHandler;
90  import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
91  import edu.internet2.middleware.shibboleth.idp.session.Session;
92  
93  /** Common implementation details for profile handlers. */
94  public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHandler {
95  
96      /** SAML Version for this profile handler. */
97      public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_20;
98  
99      /** Class logger. */
100     private Logger log = LoggerFactory.getLogger(AbstractSAML2ProfileHandler.class);
101 
102     /** For building response. */
103     private SAMLObjectBuilder<Response> responseBuilder;
104 
105     /** For building status. */
106     private SAMLObjectBuilder<Status> statusBuilder;
107 
108     /** For building statuscode. */
109     private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
110 
111     /** For building StatusMessages. */
112     private SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
113 
114     /** For building assertion. */
115     private SAMLObjectBuilder<Assertion> assertionBuilder;
116 
117     /** For building issuer. */
118     private SAMLObjectBuilder<Issuer> issuerBuilder;
119 
120     /** For building subject. */
121     private SAMLObjectBuilder<Subject> subjectBuilder;
122 
123     /** For building subject confirmation. */
124     private SAMLObjectBuilder<SubjectConfirmation> subjectConfirmationBuilder;
125 
126     /** For building subject confirmation data. */
127     private SAMLObjectBuilder<SubjectConfirmationData> subjectConfirmationDataBuilder;
128 
129     /** For building conditions. */
130     private SAMLObjectBuilder<Conditions> conditionsBuilder;
131 
132     /** For building audience restriction. */
133     private SAMLObjectBuilder<AudienceRestriction> audienceRestrictionBuilder;
134 
135     /** For building proxy restrictions. */
136     private SAMLObjectBuilder<ProxyRestriction> proxyRestrictionBuilder;
137 
138     /** For building audience. */
139     private SAMLObjectBuilder<Audience> audienceBuilder;
140 
141     /** For building signature. */
142     private XMLObjectBuilder<Signature> signatureBuilder;
143 
144     /** Constructor. */
145     @SuppressWarnings("unchecked")
146     protected AbstractSAML2ProfileHandler() {
147         super();
148 
149         responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
150         statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
151         statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(
152                 StatusCode.DEFAULT_ELEMENT_NAME);
153         statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(
154                 StatusMessage.DEFAULT_ELEMENT_NAME);
155         issuerBuilder = (SAMLObjectBuilder<Issuer>) getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
156         assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
157                 .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
158         subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
159         subjectConfirmationBuilder = (SAMLObjectBuilder<SubjectConfirmation>) getBuilderFactory().getBuilder(
160                 SubjectConfirmation.DEFAULT_ELEMENT_NAME);
161         subjectConfirmationDataBuilder = (SAMLObjectBuilder<SubjectConfirmationData>) getBuilderFactory().getBuilder(
162                 SubjectConfirmationData.DEFAULT_ELEMENT_NAME);
163         conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
164                 Conditions.DEFAULT_ELEMENT_NAME);
165         audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestriction>) getBuilderFactory().getBuilder(
166                 AudienceRestriction.DEFAULT_ELEMENT_NAME);
167         proxyRestrictionBuilder = (SAMLObjectBuilder<ProxyRestriction>) getBuilderFactory().getBuilder(
168                 ProxyRestriction.DEFAULT_ELEMENT_NAME);
169         audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
170         signatureBuilder = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
171     }
172 
173     /** {@inheritDoc} */
174     protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
175         BaseSAML2ProfileRequestContext saml2Request = (BaseSAML2ProfileRequestContext) requestContext;
176         try {
177             super.populateRequestContext(requestContext);
178         } catch (ProfileException e) {
179             if (saml2Request.getFailureStatus() == null) {
180                 saml2Request.setFailureStatus(buildStatus(StatusCode.REQUESTER_URI, null, e.getMessage()));
181             }
182             throw e;
183         }
184     }
185 
186     /**
187      * Populates the request context with the information about the user.
188      * 
189      * This method requires the the following request context properties to be populated: inbound message transport,
190      * relying party ID
191      * 
192      * This methods populates the following request context properties: user's session, user's principal name, and
193      * service authentication method
194      * 
195      * @param requestContext current request context
196      */
197     protected void populateUserInformation(BaseSAMLProfileRequestContext requestContext) {
198         Session userSession = getUserSession(requestContext.getInboundMessageTransport());
199         if (userSession == null) {
200             NameID subject = (NameID) requestContext.getSubjectNameIdentifier();
201             if (subject != null && subject.getValue() != null) {
202                 userSession = getUserSession(subject.getValue());
203             }
204         }
205 
206         if (userSession != null) {
207             requestContext.setUserSession(userSession);
208             requestContext.setPrincipalName(userSession.getPrincipalName());
209             ServiceInformation serviceInfo = userSession.getServicesInformation().get(
210                     requestContext.getInboundMessageIssuer());
211             if (serviceInfo != null) {
212                 requestContext.setPrincipalAuthenticationMethod(serviceInfo.getAuthenticationMethod()
213                         .getAuthenticationMethod());
214             }
215         }
216     }
217 
218     /**
219      * Checks that the SAML major version for a request is 2.
220      * 
221      * @param requestContext current request context containing the SAML message
222      * 
223      * @throws ProfileException thrown if the major version of the SAML request is not 2
224      */
225     protected void checkSamlVersion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
226         SAMLVersion version = requestContext.getInboundSAMLMessage().getVersion();
227         if (version.getMajorVersion() < 2) {
228             requestContext.setFailureStatus(buildStatus(StatusCode.VERSION_MISMATCH_URI,
229                     StatusCode.REQUEST_VERSION_TOO_LOW_URI, null));
230             throw new ProfileException("SAML request version too low");
231         } else if (version.getMajorVersion() > 2 || version.getMinorVersion() > 0) {
232             requestContext.setFailureStatus(buildStatus(StatusCode.VERSION_MISMATCH_URI,
233                     StatusCode.REQUEST_VERSION_TOO_HIGH_URI, null));
234             throw new ProfileException("SAML request version too high");
235         }
236     }
237 
238     /**
239      * Builds a response to the attribute query within the request context.
240      * 
241      * @param requestContext current request context
242      * @param subjectConfirmationMethod confirmation method used for the subject
243      * @param statements the statements to include in the response
244      * 
245      * @return the built response
246      * 
247      * @throws ProfileException thrown if there is a problem creating the SAML response
248      */
249     protected Response buildResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext,
250             String subjectConfirmationMethod, List<Statement> statements) throws ProfileException {
251 
252         DateTime issueInstant = new DateTime();
253 
254         Response samlResponse = responseBuilder.buildObject();
255         samlResponse.setIssueInstant(issueInstant);
256         populateStatusResponse(requestContext, samlResponse);
257 
258         Assertion assertion = null;
259         if (statements != null && !statements.isEmpty()) {
260             assertion = buildAssertion(requestContext, issueInstant);
261             assertion.getStatements().addAll(statements);
262             assertion.setSubject(buildSubject(requestContext, subjectConfirmationMethod, issueInstant));
263 
264             signAssertion(requestContext, assertion);
265 
266             SAMLMessageEncoder encoder = getMessageEncoders().get(requestContext.getPeerEntityEndpoint().getBinding());
267             try {
268                 if (requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.always
269                         || (requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.conditional && !encoder
270                                 .providesMessageConfidentiality(requestContext))) {
271                     log.debug("Attempting to encrypt assertion to relying party {}", requestContext
272                             .getInboundMessageIssuer());
273                     try {
274                         Encrypter encrypter = getEncrypter(requestContext.getInboundMessageIssuer());
275                         samlResponse.getEncryptedAssertions().add(encrypter.encrypt(assertion));
276                     } catch (SecurityException e) {
277                         log.error("Unable to construct encrypter", e);
278                         requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
279                                 "Unable to encrypt assertion"));
280                         throw new ProfileException("Unable to construct encrypter", e);
281                     } catch (EncryptionException e) {
282                         log.error("Unable to encrypt assertion", e);
283                         requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
284                                 "Unable to encrypt assertion"));
285                         throw new ProfileException("Unable to encrypt assertion", e);
286                     }
287                 } else {
288                     samlResponse.getAssertions().add(assertion);
289                 }
290             } catch (MessageEncodingException e) {
291                 log.error("Unable to determine if outbound encoding {} can provide confidentiality", encoder
292                         .getBindingURI());
293                 throw new ProfileException("Unable to determine if assertions should be encrypted");
294             }
295         }
296 
297         Status status = buildStatus(StatusCode.SUCCESS_URI, null, null);
298         samlResponse.setStatus(status);
299 
300         return samlResponse;
301     }
302 
303     /**
304      * Builds a basic assertion with its id, issue instant, SAML version, issuer, subject, and conditions populated.
305      * 
306      * @param requestContext current request context
307      * @param issueInstant time to use as assertion issue instant
308      * 
309      * @return the built assertion
310      */
311     protected Assertion buildAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, DateTime issueInstant) {
312         Assertion assertion = assertionBuilder.buildObject();
313         assertion.setID(getIdGenerator().generateIdentifier());
314         assertion.setIssueInstant(issueInstant);
315         assertion.setVersion(SAMLVersion.VERSION_20);
316         assertion.setIssuer(buildEntityIssuer(requestContext));
317 
318         Conditions conditions = buildConditions(requestContext, issueInstant);
319         assertion.setConditions(conditions);
320 
321         return assertion;
322     }
323 
324     /**
325      * Creates an {@link Issuer} populated with information about the relying party.
326      * 
327      * @param requestContext current request context
328      * 
329      * @return the built issuer
330      */
331     protected Issuer buildEntityIssuer(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) {
332         Issuer issuer = issuerBuilder.buildObject();
333         issuer.setFormat(Issuer.ENTITY);
334         issuer.setValue(requestContext.getLocalEntityId());
335 
336         return issuer;
337     }
338 
339     /**
340      * Builds a SAML assertion condition set. The following fields are set; not before, not on or after, audience
341      * restrictions, and proxy restrictions.
342      * 
343      * @param requestContext current request context
344      * @param issueInstant timestamp the assertion was created
345      * 
346      * @return constructed conditions
347      */
348     protected Conditions buildConditions(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, DateTime issueInstant) {
349         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
350 
351         Conditions conditions = conditionsBuilder.buildObject();
352         conditions.setNotBefore(issueInstant);
353         conditions.setNotOnOrAfter(issueInstant.plus(profileConfig.getAssertionLifetime()));
354 
355         Collection<String> audiences;
356 
357         // add audience restrictions
358         AudienceRestriction audienceRestriction = audienceRestrictionBuilder.buildObject();
359         // TODO we should only do this for certain outgoing bindings, not globally
360         Audience audience = audienceBuilder.buildObject();
361         audience.setAudienceURI(requestContext.getInboundMessageIssuer());
362         audienceRestriction.getAudiences().add(audience);
363         audiences = profileConfig.getAssertionAudiences();
364         if (audiences != null && audiences.size() > 0) {
365             for (String audienceUri : audiences) {
366                 audience = audienceBuilder.buildObject();
367                 audience.setAudienceURI(audienceUri);
368                 audienceRestriction.getAudiences().add(audience);
369             }
370         }
371         conditions.getAudienceRestrictions().add(audienceRestriction);
372 
373         // add proxy restrictions
374         audiences = profileConfig.getProxyAudiences();
375         if (audiences != null && audiences.size() > 0) {
376             ProxyRestriction proxyRestriction = proxyRestrictionBuilder.buildObject();
377             for (String audienceUri : audiences) {
378                 audience = audienceBuilder.buildObject();
379                 audience.setAudienceURI(audienceUri);
380                 proxyRestriction.getAudiences().add(audience);
381             }
382 
383             proxyRestriction.setProxyCount(profileConfig.getProxyCount());
384             conditions.getConditions().add(proxyRestriction);
385         }
386 
387         return conditions;
388     }
389 
390     /**
391      * Populates the response's id, in response to, issue instant, version, and issuer properties.
392      * 
393      * @param requestContext current request context
394      * @param response the response to populate
395      */
396     protected void populateStatusResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext,
397             StatusResponseType response) {
398         response.setID(getIdGenerator().generateIdentifier());
399 
400         response.setInResponseTo(requestContext.getInboundSAMLMessageId());
401         response.setIssuer(buildEntityIssuer(requestContext));
402 
403         response.setVersion(SAMLVersion.VERSION_20);
404     }
405 
406     /**
407      * Resolves the attributes for the principal.
408      * 
409      * @param requestContext current request context
410      * 
411      * @throws ProfileException thrown if there is a problem resolved attributes
412      */
413     protected void resolveAttributes(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
414         AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
415         SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
416         try {
417             log.debug("Resolving attributes for principal {} of SAML request from relying party {}", requestContext
418                     .getPrincipalName(), requestContext.getInboundMessageIssuer());
419             Map<String, BaseAttribute> principalAttributes = attributeAuthority.getAttributes(requestContext);
420 
421             requestContext.setAttributes(principalAttributes);
422         } catch (AttributeRequestException e) {
423             log.error("Error resolving attributes for SAML request " + requestContext.getInboundSAMLMessageId()
424                     + " from relying party " + requestContext.getInboundMessageIssuer(), e);
425             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Error resolving attributes"));
426             throw new ProfileException("Error resolving attributes for SAML request "
427                     + requestContext.getInboundSAMLMessageId() + " from relying party "
428                     + requestContext.getInboundMessageIssuer(), e);
429         }
430     }
431 
432     /**
433      * Executes a query for attributes and builds a SAML attribute statement from the results.
434      * 
435      * @param requestContext current request context
436      * 
437      * @return attribute statement resulting from the query
438      * 
439      * @throws ProfileException thrown if there is a problem making the query
440      */
441     protected AttributeStatement buildAttributeStatement(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext)
442             throws ProfileException {
443         log.debug("Creating attribute statement in response to SAML request {} from relying party {}", requestContext
444                 .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
445 
446         AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
447         SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
448         try {
449             if (requestContext.getInboundSAMLMessage() instanceof AttributeQuery) {
450                 return attributeAuthority.buildAttributeStatement((AttributeQuery) requestContext
451                         .getInboundSAMLMessage(), requestContext.getAttributes().values());
452             } else {
453                 return attributeAuthority.buildAttributeStatement(null, requestContext.getAttributes().values());
454             }
455         } catch (AttributeRequestException e) {
456             log.error("Error encoding attributes for principal " + requestContext.getPrincipalName(), e);
457             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Error resolving attributes"));
458             throw new ProfileException("Error encoding attributes for principal " + requestContext.getPrincipalName(),
459                     e);
460         }
461     }
462 
463     /**
464      * Resolves the principal name of the subject of the request.
465      * 
466      * @param requestContext current request context
467      * 
468      * @throws ProfileException thrown if the principal name can not be resolved
469      */
470     protected void resolvePrincipal(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
471         AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
472         if (profileConfiguration == null) {
473             log.error("Unable to resolve principal, no SAML 2 profile configuration for relying party "
474                     + requestContext.getInboundMessageIssuer());
475             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.REQUEST_DENIED_URI,
476                     "Error resolving principal"));
477             throw new ProfileException(
478                     "Unable to resolve principal, no SAML 2 profile configuration for relying party "
479                             + requestContext.getInboundMessageIssuer());
480         }
481         SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
482         log.debug("Resolving principal name for subject of SAML request {} from relying party {}", requestContext
483                 .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
484 
485         try {
486             String principal = attributeAuthority.getPrincipal(requestContext);
487             requestContext.setPrincipalName(principal);
488         } catch (AttributeRequestException e) {
489             log.error("Error resolving attributes for SAML request " + requestContext.getInboundSAMLMessageId()
490                     + " from relying party " + requestContext.getInboundMessageIssuer(), e);
491             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.UNKNOWN_PRINCIPAL_URI,
492                     "Error resolving principal"));
493             throw new ProfileException("Error resolving attributes for SAML request "
494                     + requestContext.getInboundSAMLMessageId() + " from relying party "
495                     + requestContext.getInboundMessageIssuer(), e);
496         }
497     }
498 
499     /**
500      * Signs the given assertion if either the current profile configuration or the relying party configuration contains
501      * signing credentials.
502      * 
503      * @param requestContext current request context
504      * @param assertion assertion to sign
505      * 
506      * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
507      *             required, if a signing credential is not configured
508      */
509     protected void signAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Assertion assertion)
510             throws ProfileException {
511         log.debug("Determining if SAML assertion to relying party {} should be signed", requestContext
512                 .getInboundMessageIssuer());
513 
514         boolean signAssertion = false;
515 
516         SAMLMessageEncoder encoder = getMessageEncoders().get(requestContext.getPeerEntityEndpoint().getBinding());
517         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
518         try {
519             if (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.always
520                     || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional && !encoder
521                             .providesMessageIntegrity(requestContext))) {
522                 signAssertion = true;
523                 log.debug("IdP relying party configuration {} indicates to sign assertions: {}", requestContext
524                         .getRelyingPartyConfiguration().getRelyingPartyId(), signAssertion);
525             }
526         } catch (MessageEncodingException e) {
527             log.error("Unable to determine if outbound encoding {} can provide integrity protection", encoder
528                     .getBindingURI());
529             throw new ProfileException("Unable to determine if outbound message should be signed");
530         }
531 
532         if (!signAssertion && requestContext.getPeerEntityRoleMetadata() instanceof SPSSODescriptor) {
533             SPSSODescriptor ssoDescriptor = (SPSSODescriptor) requestContext.getPeerEntityRoleMetadata();
534             if (ssoDescriptor.getWantAssertionsSigned() != null) {
535                 signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
536                 log.debug("Entity metadata for relying party {} indicates to sign assertions: {}", requestContext
537                         .getInboundMessageIssuer(), signAssertion);
538             }
539         }
540 
541         if (!signAssertion) {
542             return;
543         }
544 
545         log.debug("Determining signing credntial for assertion to relying party {}", requestContext
546                 .getInboundMessageIssuer());
547         Credential signatureCredential = profileConfig.getSigningCredential();
548         if (signatureCredential == null) {
549             signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
550         }
551 
552         if (signatureCredential == null) {
553             throw new ProfileException("No signing credential is specified for relying party configuration "
554                     + requestContext.getRelyingPartyConfiguration().getProviderId()
555                     + " or it's SAML2 attribute query profile configuration");
556         }
557 
558         log.debug("Signing assertion to relying party {}", requestContext.getInboundMessageIssuer());
559         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
560 
561         signature.setSigningCredential(signatureCredential);
562         try {
563             // TODO pull SecurityConfiguration from SAMLMessageContext? needs to be added
564             // TODO how to pull what keyInfoGenName to use?
565             SecurityHelper.prepareSignatureParams(signature, signatureCredential, null, null);
566         } catch (SecurityException e) {
567             throw new ProfileException("Error preparing signature for signing", e);
568         }
569 
570         assertion.setSignature(signature);
571 
572         Marshaller assertionMarshaller = Configuration.getMarshallerFactory().getMarshaller(assertion);
573         try {
574             assertionMarshaller.marshall(assertion);
575             Signer.signObject(signature);
576         } catch (MarshallingException e) {
577             log.error("Unable to marshall assertion for signing", e);
578             throw new ProfileException("Unable to marshall assertion for signing", e);
579         } catch (SignatureException e) {
580             log.error("Unable to sign assertion", e);
581             throw new ProfileException("Unable to sign assertion", e);
582         }
583     }
584 
585     /**
586      * Build a status message, with an optional second-level failure message.
587      * 
588      * @param topLevelCode The top-level status code. Should be from saml-core-2.0-os, sec. 3.2.2.2
589      * @param secondLevelCode An optional second-level failure code. Should be from saml-core-2.0-is, sec 3.2.2.2. If
590      *            null, no second-level Status element will be set.
591      * @param failureMessage An optional second-level failure message
592      * 
593      * @return a Status object.
594      */
595     protected Status buildStatus(String topLevelCode, String secondLevelCode, String failureMessage) {
596         Status status = statusBuilder.buildObject();
597 
598         StatusCode statusCode = statusCodeBuilder.buildObject();
599         statusCode.setValue(DatatypeHelper.safeTrimOrNullString(topLevelCode));
600         status.setStatusCode(statusCode);
601 
602         if (secondLevelCode != null) {
603             StatusCode secondLevelStatusCode = statusCodeBuilder.buildObject();
604             secondLevelStatusCode.setValue(DatatypeHelper.safeTrimOrNullString(secondLevelCode));
605             statusCode.setStatusCode(secondLevelStatusCode);
606         }
607 
608         if (failureMessage != null) {
609             StatusMessage msg = statusMessageBuilder.buildObject();
610             msg.setMessage(failureMessage);
611             status.setStatusMessage(msg);
612         }
613 
614         return status;
615     }
616 
617     /**
618      * Builds the SAML subject for the user for the service provider.
619      * 
620      * @param requestContext current request context
621      * @param confirmationMethod subject confirmation method used for the subject
622      * @param issueInstant instant the subject confirmation data should reflect for issuance
623      * 
624      * @return SAML subject for the user for the service provider
625      * 
626      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
627      *             name ID attribute or because there are no supported name formats
628      */
629     protected Subject buildSubject(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, String confirmationMethod,
630             DateTime issueInstant) throws ProfileException {
631         Subject subject = subjectBuilder.buildObject();
632         subject.getSubjectConfirmations().add(
633                 buildSubjectConfirmation(requestContext, confirmationMethod, issueInstant));
634 
635         NameID nameID = buildNameId(requestContext);
636         if (nameID == null) {
637             return subject;
638         }
639 
640         requestContext.setSubjectNameIdentifier(nameID);
641 
642         boolean nameIdEncRequiredByAuthnRequest = false;
643         if (requestContext.getInboundSAMLMessage() instanceof AuthnRequest) {
644             AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
645             NameIDPolicy policy = authnRequest.getNameIDPolicy();
646             if (policy != null && DatatypeHelper.safeEquals(policy.getFormat(), NameID.ENCRYPTED)) {
647                 nameIdEncRequiredByAuthnRequest = true;
648             }
649         }
650 
651         SAMLMessageEncoder encoder = getMessageEncoders().get(requestContext.getPeerEntityEndpoint().getBinding());
652         try {
653             if (nameIdEncRequiredByAuthnRequest
654                     || requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.always
655                     || (requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.conditional && !encoder
656                             .providesMessageConfidentiality(requestContext))) {
657                 log.debug("Attempting to encrypt NameID to relying party {}", requestContext.getInboundMessageIssuer());
658                 try {
659                     Encrypter encrypter = getEncrypter(requestContext.getInboundMessageIssuer());
660                     subject.setEncryptedID(encrypter.encrypt(nameID));
661                 } catch (SecurityException e) {
662                     log.error("Unable to construct encrypter", e);
663                     requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
664                             "Unable to encrypt NameID"));
665                     throw new ProfileException("Unable to construct encrypter", e);
666                 } catch (EncryptionException e) {
667                     log.error("Unable to encrypt NameID", e);
668                     requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
669                             "Unable to encrypt NameID"));
670                     throw new ProfileException("Unable to encrypt NameID", e);
671                 }
672             } else {
673                 subject.setNameID(nameID);
674             }
675         } catch (MessageEncodingException e) {
676             log.error("Unable to determine if outbound encoding {} can provide confidentiality", encoder
677                     .getBindingURI());
678             throw new ProfileException("Unable to determine if assertions should be encrypted");
679         }
680 
681         return subject;
682     }
683 
684     /**
685      * Builds the SubjectConfirmation appropriate for this request.
686      * 
687      * @param requestContext current request context
688      * @param confirmationMethod confirmation method to use for the request
689      * @param issueInstant issue instant of the response
690      * 
691      * @return the constructed subject confirmation
692      */
693     protected SubjectConfirmation buildSubjectConfirmation(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext,
694             String confirmationMethod, DateTime issueInstant) {
695         SubjectConfirmationData confirmationData = subjectConfirmationDataBuilder.buildObject();
696         HTTPInTransport inTransport = (HTTPInTransport) requestContext.getInboundMessageTransport();
697         confirmationData.setAddress(inTransport.getPeerAddress());
698         confirmationData.setInResponseTo(requestContext.getInboundSAMLMessageId());
699         confirmationData.setNotOnOrAfter(issueInstant.plus(requestContext.getProfileConfiguration()
700                 .getAssertionLifetime()));
701 
702         Endpoint relyingPartyEndpoint = requestContext.getPeerEntityEndpoint();
703         if (relyingPartyEndpoint != null) {
704             if (relyingPartyEndpoint.getResponseLocation() != null) {
705                 confirmationData.setRecipient(relyingPartyEndpoint.getResponseLocation());
706             } else {
707                 confirmationData.setRecipient(relyingPartyEndpoint.getLocation());
708             }
709         }
710 
711         SubjectConfirmation subjectConfirmation = subjectConfirmationBuilder.buildObject();
712         subjectConfirmation.setMethod(confirmationMethod);
713         subjectConfirmation.setSubjectConfirmationData(confirmationData);
714 
715         return subjectConfirmation;
716     }
717 
718     /**
719      * Builds a NameID appropriate for this request. NameIDs are built by inspecting the SAML request and metadata,
720      * picking a name format that was requested by the relying party or is mutually supported by both the relying party
721      * and asserting party as described in their metadata entries. Once a set of supported name formats is determined
722      * the principals attributes are inspected for an attribute supported an attribute encoder whose category is one of
723      * the supported name formats.
724      * 
725      * @param requestContext current request context
726      * 
727      * @return the NameID appropriate for this request
728      * 
729      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
730      *             name ID attribute or because there are no supported name formats
731      */
732     protected NameID buildNameId(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
733         log.debug("Building assertion NameID for principal/relying party:{}/{}", requestContext.getPrincipalName(),
734                 requestContext.getInboundMessageIssuer());
735 
736         // Check if AuthnRequest includes an explicit NameIDPolicy Format.
737         String requiredNameFormat = null;
738         if (requestContext.getInboundSAMLMessage() instanceof AuthnRequest) {
739             AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
740             if (authnRequest.getNameIDPolicy() != null) {
741                 requiredNameFormat = DatatypeHelper.safeTrimOrNullString(authnRequest.getNameIDPolicy().getFormat());
742                 // Check for unspec'd or encryption formats, which aren't relevant for this section of code.
743                 if (requiredNameFormat != null
744                         && (requiredNameFormat.equals("urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted") || requiredNameFormat
745                                 .equals(NameID.UNSPECIFIED))) {
746                     requiredNameFormat = null;
747                 }
748             }
749         }
750 
751         // Get the SP's list, and filter it down by the AuthnRequest if need be.
752         List<String> supportedNameFormats = getNameFormats(requestContext);
753         if (requiredNameFormat != null) {
754             supportedNameFormats.clear();
755             supportedNameFormats.add(requiredNameFormat);
756         }
757 
758         Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
759         if (principalAttributes == null || principalAttributes.isEmpty()) {
760             if (requiredNameFormat != null) {
761                 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI,
762                         StatusCode.INVALID_NAMEID_POLICY_URI, "Format not supported: " + requiredNameFormat));
763                 throw new ProfileException(
764                         "No attributes for principal, so NameID format required by relying party is not supported");
765             }
766             log.debug("No attributes for principal {}, no name identifier will be created.", requestContext
767                     .getPrincipalName());
768             return null;
769         }
770 
771         if (!supportedNameFormats.isEmpty()) {
772             log.debug("SP-supported name formats: {}", supportedNameFormats);
773         } else {
774             log.debug("SP indicated no preferred name formats.");
775         }
776 
777         try {
778             SAML2NameIDEncoder nameIdEncoder;
779             for (BaseAttribute<?> attribute : principalAttributes.values()) {
780                 for (AttributeEncoder encoder : attribute.getEncoders()) {
781                     if (encoder instanceof SAML2NameIDEncoder) {
782                         nameIdEncoder = (SAML2NameIDEncoder) encoder;
783                         if (supportedNameFormats.isEmpty()
784                                 || supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
785                             log.debug("Using attribute {} supporting NameID format {} to create the NameID.", attribute
786                                     .getId(), nameIdEncoder.getNameFormat());
787                             return nameIdEncoder.encode(attribute);
788                         }
789                     }
790                 }
791             }
792 
793             if (requiredNameFormat != null) {
794                 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI,
795                         StatusCode.INVALID_NAMEID_POLICY_URI, "Format not supported: " + requiredNameFormat));
796                 throw new ProfileException(
797                         "No attributes for principal support NameID format required by relying party");
798             }
799             log.debug("No attributes for principal {} support an encoding into a supported name ID format.",
800                     requestContext.getPrincipalName());
801             return null;
802         } catch (AttributeEncodingException e) {
803             log.error("Unable to encode NameID attribute", e);
804             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to construct NameID"));
805             throw new ProfileException("Unable to encode NameID attribute", e);
806         }
807     }
808 
809     /**
810      * Constructs an SAML response message carrying a request error.
811      * 
812      * @param requestContext current request context
813      * 
814      * @return the constructed error response
815      */
816     protected Response buildErrorResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) {
817         Response samlResponse = responseBuilder.buildObject();
818         samlResponse.setIssueInstant(new DateTime());
819         populateStatusResponse(requestContext, samlResponse);
820 
821         samlResponse.setStatus(requestContext.getFailureStatus());
822 
823         return samlResponse;
824     }
825 
826     /**
827      * Gets an encrypter that may be used encrypt content to a given peer.
828      * 
829      * @param peerEntityId entity ID of the peer
830      * 
831      * @return encrypter that may be used encrypt content to a given peer
832      * 
833      * @throws SecurityException thrown if there is a problem constructing the encrypter. This normally occurs if the
834      *             key encryption credential for the peer can not be resolved or a required encryption algorithm is not
835      *             supported by the VM's JCE.
836      */
837     protected Encrypter getEncrypter(String peerEntityId) throws SecurityException {
838         SecurityConfiguration securityConfiguration = Configuration.getGlobalSecurityConfiguration();
839 
840         EncryptionParameters dataEncParams = SecurityHelper
841                 .buildDataEncryptionParams(null, securityConfiguration, null);
842 
843         Credential keyEncryptionCredential = getKeyEncryptionCredential(peerEntityId);
844         if (keyEncryptionCredential == null) {
845             log.error("Could not resolve a key encryption credential for peer entity: {}", peerEntityId);
846             throw new SecurityException("Could not resolve key encryption credential");
847         }
848         String wrappedJCAKeyAlgorithm = SecurityHelper.getKeyAlgorithmFromURI(dataEncParams.getAlgorithm());
849         KeyEncryptionParameters keyEncParams = SecurityHelper.buildKeyEncryptionParams(keyEncryptionCredential,
850                 wrappedJCAKeyAlgorithm, securityConfiguration, null, null);
851 
852         Encrypter encrypter = new Encrypter(dataEncParams, keyEncParams);
853         encrypter.setKeyPlacement(KeyPlacement.INLINE);
854         return encrypter;
855     }
856 
857     /**
858      * Gets the credential that can be used to encrypt encryption keys for a peer.
859      * 
860      * @param peerEntityId entity ID of the peer
861      * 
862      * @return credential that can be used to encrypt encryption keys for a peer
863      * 
864      * @throws SecurityException thrown if there is a problem resolving the credential from the peer's metadata
865      */
866     protected Credential getKeyEncryptionCredential(String peerEntityId) throws SecurityException {
867         MetadataCredentialResolver kekCredentialResolver = new MetadataCredentialResolver(getMetadataProvider());
868 
869         CriteriaSet criteriaSet = new CriteriaSet();
870         criteriaSet.add(new EntityIDCriteria(peerEntityId));
871         criteriaSet.add(new MetadataCriteria(SPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS));
872         criteriaSet.add(new UsageCriteria(UsageType.ENCRYPTION));
873 
874         return kekCredentialResolver.resolveSingle(criteriaSet);
875     }
876 
877     /**
878      * Writes an audit log entry indicating the successful response to the attribute request.
879      * 
880      * @param context current request context
881      */
882     protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
883         SAML2AuditLogEntry auditLogEntry = new SAML2AuditLogEntry();
884         auditLogEntry.setSAMLResponse((StatusResponseType) context.getOutboundSAMLMessage());
885         auditLogEntry.setMessageProfile(getProfileId());
886         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
887         auditLogEntry.setPrincipalName(context.getPrincipalName());
888         auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
889         auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
890         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
891         auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
892         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
893         auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
894         if (context.getReleasedAttributes() != null) {
895             auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
896         }
897 
898         getAduitLog().info(auditLogEntry.toString());
899     }
900 
901     /** SAML 1 specific audit log entry. */
902     protected class SAML2AuditLogEntry extends AuditLogEntry {
903 
904         /** The response to the SAML request. */
905         private StatusResponseType samlResponse;
906 
907         /**
908          * Gets the response to the SAML request.
909          * 
910          * @return the response to the SAML request
911          */
912         public StatusResponseType getSAMLResponse() {
913             return samlResponse;
914         }
915 
916         /**
917          * Sets the response to the SAML request.
918          * 
919          * @param response the response to the SAML request
920          */
921         public void setSAMLResponse(StatusResponseType response) {
922             samlResponse = response;
923         }
924 
925         /** {@inheritDoc} */
926         public String toString() {
927             StringBuilder entryString = new StringBuilder(super.toString());
928 
929             NameID nameIdentifier = null;
930             StringBuilder assertionIds = new StringBuilder();
931 
932             if (samlResponse instanceof Response) {
933                 List<Assertion> assertions = ((Response) samlResponse).getAssertions();
934                 if (assertions != null && !assertions.isEmpty()) {
935                     for (Assertion assertion : assertions) {
936                         assertionIds.append(assertion.getID());
937                         assertionIds.append(",");
938 
939                         if (nameIdentifier == null) {
940                             if (assertion.getSubject() != null) {
941                                 nameIdentifier = assertion.getSubject().getNameID();
942                             }
943                         }
944                     }
945                 }
946             }
947 
948             if (nameIdentifier != null) {
949                 entryString.append(nameIdentifier.getValue());
950             }
951             entryString.append("|");
952 
953             entryString.append(assertionIds.toString());
954             entryString.append("|");
955 
956             return entryString.toString();
957         }
958     }
959 }