View Javadoc

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