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 }