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         if (requestContext != null && requestContext.getInboundSAMLMessage() != null) {
    401             response.setInResponseTo(requestContext.getInboundSAMLMessageId());
    402             response.setIssuer(buildEntityIssuer(requestContext));
    403         }
    404
    405         response.setVersion(SAMLVersion.VERSION_20);
    406     }
    407
    408     /**
    409      * Resolves the attributes for the principal.
    410      *
    411      * @param requestContext current request context
    412      *
    413      * @throws ProfileException thrown if there is a problem resolved attributes
    414      */
    415     protected void resolveAttributes(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
    416         AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
    417         SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
    418         try {
    419             log.debug("Resolving attributes for principal {} of SAML request from relying party {}", requestContext
    420                     .getPrincipalName(), requestContext.getInboundMessageIssuer());
    421             Map<String, BaseAttribute> principalAttributes = attributeAuthority.getAttributes(requestContext);
    422
    423             requestContext.setAttributes(principalAttributes);
    424         } catch (AttributeRequestException e) {
    425             log.error("Error resolving attributes for SAML request " + requestContext.getInboundSAMLMessageId()
    426                     + " from relying party " + requestContext.getInboundMessageIssuer(), e);
    427             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Error resolving attributes"));
    428             throw new ProfileException("Error resolving attributes for SAML request "
    429                     + requestContext.getInboundSAMLMessageId() + " from relying party "
    430                     + requestContext.getInboundMessageIssuer(), e);
    431         }
    432     }
    433
    434     /**
    435      * Executes a query for attributes and builds a SAML attribute statement from the results.
    436      *
    437      * @param requestContext current request context
    438      *
    439      * @return attribute statement resulting from the query
    440      *
    441      * @throws ProfileException thrown if there is a problem making the query
    442      */
    443     protected AttributeStatement buildAttributeStatement(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext)
    444             throws ProfileException {
    445         log.debug("Creating attribute statement in response to SAML request {} from relying party {}", requestContext
    446                 .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
    447
    448         AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
    449         SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
    450         try {
    451             if (requestContext.getInboundSAMLMessage() instanceof AttributeQuery) {
    452                 return attributeAuthority.buildAttributeStatement((AttributeQuery) requestContext
    453                         .getInboundSAMLMessage(), requestContext.getAttributes().values());
    454             } else {
    455                 return attributeAuthority.buildAttributeStatement(null, requestContext.getAttributes().values());
    456             }
    457         } catch (AttributeRequestException e) {
    458             log.error("Error encoding attributes for principal " + requestContext.getPrincipalName(), e);
    459             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Error resolving attributes"));
    460             throw new ProfileException("Error encoding attributes for principal " + requestContext.getPrincipalName(),
    461                     e);
    462         }
    463     }
    464
    465     /**
    466      * Resolves the principal name of the subject of the request.
    467      *
    468      * @param requestContext current request context
    469      *
    470      * @throws ProfileException thrown if the principal name can not be resolved
    471      */
    472     protected void resolvePrincipal(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
    473         AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
    474         if (profileConfiguration == null) {
    475             log.error("Unable to resolve principal, no SAML 2 profile configuration for relying party "
    476                     + requestContext.getInboundMessageIssuer());
    477             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.REQUEST_DENIED_URI,
    478                     "Error resolving principal"));
    479             throw new ProfileException(
    480                     "Unable to resolve principal, no SAML 2 profile configuration for relying party "
    481                             + requestContext.getInboundMessageIssuer());
    482         }
    483         SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
    484         log.debug("Resolving principal name for subject of SAML request {} from relying party {}", requestContext
    485                 .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
    486
    487         try {
    488             String principal = attributeAuthority.getPrincipal(requestContext);
    489             requestContext.setPrincipalName(principal);
    490         } catch (AttributeRequestException e) {
    491             log.error("Error resolving attributes for SAML request " + requestContext.getInboundSAMLMessageId()
    492                     + " from relying party " + requestContext.getInboundMessageIssuer(), e);
    493             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.UNKNOWN_PRINCIPAL_URI,
    494                     "Error resolving principal"));
    495             throw new ProfileException("Error resolving attributes for SAML request "
    496                     + requestContext.getInboundSAMLMessageId() + " from relying party "
    497                     + requestContext.getInboundMessageIssuer(), e);
    498         }
    499     }
    500
    501     /**
    502      * Signs the given assertion if either the current profile configuration or the relying party configuration contains
    503      * signing credentials.
    504      *
    505      * @param requestContext current request context
    506      * @param assertion assertion to sign
    507      *
    508      * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
    509      *             required, if a signing credential is not configured
    510      */
    511     protected void signAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Assertion assertion)
    512             throws ProfileException {
    513         log.debug("Determining if SAML assertion to relying party {} should be signed", requestContext
    514                 .getInboundMessageIssuer());
    515
    516         boolean signAssertion = false;
    517
    518         SAMLMessageEncoder encoder = getMessageEncoders().get(requestContext.getPeerEntityEndpoint().getBinding());
    519         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
    520         try {
    521             if (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.always
    522                     || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional && !encoder
    523                             .providesMessageIntegrity(requestContext))) {
    524                 signAssertion = true;
    525                 log.debug("IdP relying party configuration {} indicates to sign assertions: {}", requestContext
    526                         .getRelyingPartyConfiguration().getRelyingPartyId(), signAssertion);
    527             }
    528         } catch (MessageEncodingException e) {
    529             log.error("Unable to determine if outbound encoding {} can provide integrity protection", encoder
    530                     .getBindingURI());
    531             throw new ProfileException("Unable to determine if outbound message should be signed");
    532         }
    533
    534         if (!signAssertion && requestContext.getPeerEntityRoleMetadata() instanceof SPSSODescriptor) {
    535             SPSSODescriptor ssoDescriptor = (SPSSODescriptor) requestContext.getPeerEntityRoleMetadata();
    536             if (ssoDescriptor.getWantAssertionsSigned() != null) {
    537                 signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
    538                 log.debug("Entity metadata for relying party {} indicates to sign assertions: {}", requestContext
    539                         .getInboundMessageIssuer(), signAssertion);
    540             }
    541         }
    542
    543         if (!signAssertion) {
    544             return;
    545         }
    546
    547         log.debug("Determining signing credntial for assertion to relying party {}", requestContext
    548                 .getInboundMessageIssuer());
    549         Credential signatureCredential = profileConfig.getSigningCredential();
    550         if (signatureCredential == null) {
    551             signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
    552         }
    553
    554         if (signatureCredential == null) {
    555             throw new ProfileException("No signing credential is specified for relying party configuration "
    556                     + requestContext.getRelyingPartyConfiguration().getProviderId()
    557                     + " or it's SAML2 attribute query profile configuration");
    558         }
    559
    560         log.debug("Signing assertion to relying party {}", requestContext.getInboundMessageIssuer());
    561         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
    562
    563         signature.setSigningCredential(signatureCredential);
    564         try {
    565             // TODO pull SecurityConfiguration from SAMLMessageContext? needs to be added
    566             // TODO how to pull what keyInfoGenName to use?
    567             SecurityHelper.prepareSignatureParams(signature, signatureCredential, null, null);
    568         } catch (SecurityException e) {
    569             throw new ProfileException("Error preparing signature for signing", e);
    570         }
    571
    572         assertion.setSignature(signature);
    573
    574         Marshaller assertionMarshaller = Configuration.getMarshallerFactory().getMarshaller(assertion);
    575         try {
    576             assertionMarshaller.marshall(assertion);
    577             Signer.signObject(signature);
    578         } catch (MarshallingException e) {
    579             log.error("Unable to marshall assertion for signing", e);
    580             throw new ProfileException("Unable to marshall assertion for signing", e);
    581         } catch (SignatureException e) {
    582             log.error("Unable to sign assertion", e);
    583             throw new ProfileException("Unable to sign assertion", e);
    584         }
    585     }
    586
    587     /**
    588      * Build a status message, with an optional second-level failure message.
    589      *
    590      * @param topLevelCode The top-level status code. Should be from saml-core-2.0-os, sec. 3.2.2.2
    591      * @param secondLevelCode An optional second-level failure code. Should be from saml-core-2.0-is, sec 3.2.2.2. If
    592      *            null, no second-level Status element will be set.
    593      * @param failureMessage An optional second-level failure message
    594      *
    595      * @return a Status object.
    596      */
    597     protected Status buildStatus(String topLevelCode, String secondLevelCode, String failureMessage) {
    598         Status status = statusBuilder.buildObject();
    599
    600         StatusCode statusCode = statusCodeBuilder.buildObject();
    601         statusCode.setValue(DatatypeHelper.safeTrimOrNullString(topLevelCode));
    602         status.setStatusCode(statusCode);
    603
    604         if (secondLevelCode != null) {
    605             StatusCode secondLevelStatusCode = statusCodeBuilder.buildObject();
    606             secondLevelStatusCode.setValue(DatatypeHelper.safeTrimOrNullString(secondLevelCode));
    607             statusCode.setStatusCode(secondLevelStatusCode);
    608         }
    609
    610         if (failureMessage != null) {
    611             StatusMessage msg = statusMessageBuilder.buildObject();
    612             msg.setMessage(failureMessage);
    613             status.setStatusMessage(msg);
    614         }
    615
    616         return status;
    617     }
    618
    619     /**
    620      * Builds the SAML subject for the user for the service provider.
    621      *
    622      * @param requestContext current request context
    623      * @param confirmationMethod subject confirmation method used for the subject
    624      * @param issueInstant instant the subject confirmation data should reflect for issuance
    625      *
    626      * @return SAML subject for the user for the service provider
    627      *
    628      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
    629      *             name ID attribute or because there are no supported name formats
    630      */
    631     protected Subject buildSubject(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, String confirmationMethod,
    632             DateTime issueInstant) throws ProfileException {
    633         Subject subject = subjectBuilder.buildObject();
    634         subject.getSubjectConfirmations().add(
    635                 buildSubjectConfirmation(requestContext, confirmationMethod, issueInstant));
    636
    637         NameID nameID = buildNameId(requestContext);
    638         if (nameID == null) {
    639             return subject;
    640         }
    641
    642         requestContext.setSubjectNameIdentifier(nameID);
    643
    644         boolean nameIdEncRequiredByAuthnRequest = false;
    645         if (requestContext.getInboundSAMLMessage() instanceof AuthnRequest) {
    646             AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
    647             NameIDPolicy policy = authnRequest.getNameIDPolicy();
    648             if (policy != null && DatatypeHelper.safeEquals(policy.getFormat(), NameID.ENCRYPTED)) {
    649                 nameIdEncRequiredByAuthnRequest = true;
    650             }
    651         }
    652
    653         SAMLMessageEncoder encoder = getMessageEncoders().get(requestContext.getPeerEntityEndpoint().getBinding());
    654         try {
    655             if (nameIdEncRequiredByAuthnRequest
    656                     || requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.always
    657                     || (requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.conditional && !encoder
    658                             .providesMessageConfidentiality(requestContext))) {
    659                 log.debug("Attempting to encrypt NameID to relying party {}", requestContext.getInboundMessageIssuer());
    660                 try {
    661                     Encrypter encrypter = getEncrypter(requestContext.getInboundMessageIssuer());
    662                     subject.setEncryptedID(encrypter.encrypt(nameID));
    663                 } catch (SecurityException e) {
    664                     log.error("Unable to construct encrypter", e);
    665                     requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
    666                             "Unable to encrypt NameID"));
    667                     throw new ProfileException("Unable to construct encrypter", e);
    668                 } catch (EncryptionException e) {
    669                     log.error("Unable to encrypt NameID", e);
    670                     requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
    671                             "Unable to encrypt NameID"));
    672                     throw new ProfileException("Unable to encrypt NameID", e);
    673                 }
    674             } else {
    675                 subject.setNameID(nameID);
    676             }
    677         } catch (MessageEncodingException e) {
    678             log.error("Unable to determine if outbound encoding {} can provide confidentiality", encoder
    679                     .getBindingURI());
    680             throw new ProfileException("Unable to determine if assertions should be encrypted");
    681         }
    682
    683         return subject;
    684     }
    685
    686     /**
    687      * Builds the SubjectConfirmation appropriate for this request.
    688      *
    689      * @param requestContext current request context
    690      * @param confirmationMethod confirmation method to use for the request
    691      * @param issueInstant issue instant of the response
    692      *
    693      * @return the constructed subject confirmation
    694      */
    695     protected SubjectConfirmation buildSubjectConfirmation(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext,
    696             String confirmationMethod, DateTime issueInstant) {
    697         SubjectConfirmationData confirmationData = subjectConfirmationDataBuilder.buildObject();
    698         HTTPInTransport inTransport = (HTTPInTransport) requestContext.getInboundMessageTransport();
    699         confirmationData.setAddress(inTransport.getPeerAddress());
    700         confirmationData.setInResponseTo(requestContext.getInboundSAMLMessageId());
    701         confirmationData.setNotOnOrAfter(issueInstant.plus(requestContext.getProfileConfiguration()
    702                 .getAssertionLifetime()));
    703
    704         Endpoint relyingPartyEndpoint = requestContext.getPeerEntityEndpoint();
    705         if (relyingPartyEndpoint != null) {
    706             if (relyingPartyEndpoint.getResponseLocation() != null) {
    707                 confirmationData.setRecipient(relyingPartyEndpoint.getResponseLocation());
    708             } else {
    709                 confirmationData.setRecipient(relyingPartyEndpoint.getLocation());
    710             }
    711         }
    712
    713         SubjectConfirmation subjectConfirmation = subjectConfirmationBuilder.buildObject();
    714         subjectConfirmation.setMethod(confirmationMethod);
    715         subjectConfirmation.setSubjectConfirmationData(confirmationData);
    716
    717         return subjectConfirmation;
    718     }
    719
    720     /**
    721      * Builds a NameID appropriate for this request. NameIDs are built by inspecting the SAML request and metadata,
    722      * picking a name format that was requested by the relying party or is mutually supported by both the relying party
    723      * and asserting party as described in their metadata entries. Once a set of supported name formats is determined
    724      * the principals attributes are inspected for an attribute supported an attribute encoder whose category is one of
    725      * the supported name formats.
    726      *
    727      * @param requestContext current request context
    728      *
    729      * @return the NameID appropriate for this request
    730      *
    731      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
    732      *             name ID attribute or because there are no supported name formats
    733      */
    734     protected NameID buildNameId(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
    735         log.debug("Building assertion NameID for principal/relying party:{}/{}", requestContext.getPrincipalName(),
    736                 requestContext.getInboundMessageIssuer());
    737
    738         // Check if AuthnRequest includes an explicit NameIDPolicy Format.
    739         String requiredNameFormat = null;
    740         if (requestContext.getInboundSAMLMessage() instanceof AuthnRequest) {
    741             AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
    742             if (authnRequest.getNameIDPolicy() != null) {
    743                 requiredNameFormat = DatatypeHelper.safeTrimOrNullString(authnRequest.getNameIDPolicy().getFormat());
    744                 // Check for unspec'd or encryption formats, which aren't relevant for this section of code.
    745                 if (requiredNameFormat != null
    746                         && (requiredNameFormat.equals("urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted") || requiredNameFormat
    747                                 .equals(NameID.UNSPECIFIED))) {
    748                     requiredNameFormat = null;
    749                 }
    750             }
    751         }
    752
    753         // Get the SP's list, and filter it down by the AuthnRequest if need be.
    754         List<String> supportedNameFormats = getNameFormats(requestContext);
    755         if (requiredNameFormat != null) {
    756             supportedNameFormats.clear();
    757             supportedNameFormats.add(requiredNameFormat);
    758         }
    759
    760         Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
    761         if (principalAttributes == null || principalAttributes.isEmpty()) {
    762             if (requiredNameFormat != null) {
    763                 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI,
    764                         StatusCode.INVALID_NAMEID_POLICY_URI, "Format not supported: " + requiredNameFormat));
    765                 throw new ProfileException(
    766                         "No attributes for principal, so NameID format required by relying party is not supported");
    767             }
    768             log.debug("No attributes for principal {}, no name identifier will be created.", requestContext
    769                     .getPrincipalName());
    770             return null;
    771         }
    772
    773         if (!supportedNameFormats.isEmpty()) {
    774             log.debug("SP-supported name formats: {}", supportedNameFormats);
    775         } else {
    776             log.debug("SP indicated no preferred name formats.");
    777         }
    778
    779         try {
    780             SAML2NameIDEncoder nameIdEncoder;
    781             for (BaseAttribute<?> attribute : principalAttributes.values()) {
    782                 for (AttributeEncoder encoder : attribute.getEncoders()) {
    783                     if (encoder instanceof SAML2NameIDEncoder) {
    784                         nameIdEncoder = (SAML2NameIDEncoder) encoder;
    785                         if (supportedNameFormats.isEmpty()
    786                                 || supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
    787                             log.debug("Using attribute {} supporting NameID format {} to create the NameID.", attribute
    788                                     .getId(), nameIdEncoder.getNameFormat());
    789                             return nameIdEncoder.encode(attribute);
    790                         }
    791                     }
    792                 }
    793             }
    794
    795             if (requiredNameFormat != null) {
    796                 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI,
    797                         StatusCode.INVALID_NAMEID_POLICY_URI, "Format not supported: " + requiredNameFormat));
    798                 throw new ProfileException(
    799                         "No attributes for principal support NameID format required by relying party");
    800             }
    801             log.debug("No attributes for principal {} support an encoding into a supported name ID format.",
    802                     requestContext.getPrincipalName());
    803             return null;
    804         } catch (AttributeEncodingException e) {
    805             log.error("Unable to encode NameID attribute", e);
    806             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to construct NameID"));
    807             throw new ProfileException("Unable to encode NameID attribute", e);
    808         }
    809     }
    810
    811     /**
    812      * Constructs an SAML response message carrying a request error.
    813      *
    814      * @param requestContext current request context
    815      *
    816      * @return the constructed error response
    817      */
    818     protected Response buildErrorResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) {
    819         Response samlResponse = responseBuilder.buildObject();
    820         samlResponse.setIssueInstant(new DateTime());
    821         populateStatusResponse(requestContext, samlResponse);
    822
    823         samlResponse.setStatus(requestContext.getFailureStatus());
    824
    825         return samlResponse;
    826     }
    827
    828     /**
    829      * Gets an encrypter that may be used encrypt content to a given peer.
    830      *
    831      * @param peerEntityId entity ID of the peer
    832      *
    833      * @return encrypter that may be used encrypt content to a given peer
    834      *
    835      * @throws SecurityException thrown if there is a problem constructing the encrypter. This normally occurs if the
    836      *             key encryption credential for the peer can not be resolved or a required encryption algorithm is not
    837      *             supported by the VM's JCE.
    838      */
    839     protected Encrypter getEncrypter(String peerEntityId) throws SecurityException {
    840         SecurityConfiguration securityConfiguration = Configuration.getGlobalSecurityConfiguration();
    841
    842         EncryptionParameters dataEncParams = SecurityHelper
    843                 .buildDataEncryptionParams(null, securityConfiguration, null);
    844
    845         Credential keyEncryptionCredential = getKeyEncryptionCredential(peerEntityId);
    846         if (keyEncryptionCredential == null) {
    847             log.error("Could not resolve a key encryption credential for peer entity: {}", peerEntityId);
    848             throw new SecurityException("Could not resolve key encryption credential");
    849         }
    850         String wrappedJCAKeyAlgorithm = SecurityHelper.getKeyAlgorithmFromURI(dataEncParams.getAlgorithm());
    851         KeyEncryptionParameters keyEncParams = SecurityHelper.buildKeyEncryptionParams(keyEncryptionCredential,
    852                 wrappedJCAKeyAlgorithm, securityConfiguration, null, null);
    853
    854         Encrypter encrypter = new Encrypter(dataEncParams, keyEncParams);
    855         encrypter.setKeyPlacement(KeyPlacement.INLINE);
    856         return encrypter;
    857     }
    858
    859     /**
    860      * Gets the credential that can be used to encrypt encryption keys for a peer.
    861      *
    862      * @param peerEntityId entity ID of the peer
    863      *
    864      * @return credential that can be used to encrypt encryption keys for a peer
    865      *
    866      * @throws SecurityException thrown if there is a problem resolving the credential from the peer's metadata
    867      */
    868     protected Credential getKeyEncryptionCredential(String peerEntityId) throws SecurityException {
    869         MetadataCredentialResolver kekCredentialResolver = new MetadataCredentialResolver(getMetadataProvider());
    870
    871         CriteriaSet criteriaSet = new CriteriaSet();
    872         criteriaSet.add(new EntityIDCriteria(peerEntityId));
    873         criteriaSet.add(new MetadataCriteria(SPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS));
    874         criteriaSet.add(new UsageCriteria(UsageType.ENCRYPTION));
    875
    876         return kekCredentialResolver.resolveSingle(criteriaSet);
    877     }
    878
    879
    880     /**
    881      * Writes an audit log entry indicating the successful response to the attribute request.
    882      *
    883      * @param context current request context
    884      */
    885     protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
    886         SAML2AuditLogEntry auditLogEntry = new SAML2AuditLogEntry();
    887         auditLogEntry.setSAMLResponse((StatusResponseType) context.getOutboundSAMLMessage());
    888         auditLogEntry.setMessageProfile(getProfileId());
    889         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
    890         auditLogEntry.setPrincipalName(context.getPrincipalName());
    891         auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
    892         auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
    893         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
    894         auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
    895         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
    896         auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
    897         if (context.getReleasedAttributes() != null) {
    898             auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
    899         }
    900
    901         getAduitLog().info(auditLogEntry.toString());
    902     }
    903
    904     /** SAML 1 specific audit log entry. */
    905     protected class SAML2AuditLogEntry extends AuditLogEntry {
    906
    907         /** The response to the SAML request. */
    908         private StatusResponseType samlResponse;
    909
    910         /**
    911          * Gets the response to the SAML request.
    912          *
    913          * @return the response to the SAML request
    914          */
    915         public StatusResponseType getSAMLResponse() {
    916             return samlResponse;
    917         }
    918
    919         /**
    920          * Sets the response to the SAML request.
    921          *
    922          * @param response the response to the SAML request
    923          */
    924         public void setSAMLResponse(StatusResponseType response) {
    925             samlResponse = response;
    926         }
    927
    928         /** {@inheritDoc} */
    929         public String toString() {
    930             StringBuilder entryString = new StringBuilder(super.toString());
    931
    932             NameID nameIdentifier = null;
    933             StringBuilder assertionIds = new StringBuilder();
    934
    935             if(samlResponse instanceof Response){
    936             List<Assertion> assertions = ((Response)samlResponse).getAssertions();
    937             if(assertions != null && !assertions.isEmpty()){
    938                 for(Assertion assertion : assertions){
    939                     assertionIds.append(assertion.getID());
    940                     assertionIds.append(",");
    941
    942                     if(nameIdentifier == null){
    943                         if(assertion.getSubject() != null){
    944                             nameIdentifier = assertion.getSubject().getNameID();
    945                         }
    946                     }
    947                 }
    948             }
    949             }
    950
    951             if(nameIdentifier != null){
    952                 entryString.append(nameIdentifier.getValue());
    953             }
    954             entryString.append("|");
    955
    956             entryString.append(assertionIds.toString());
    957             entryString.append("|");
    958
    959             return entryString.toString();
    960         }
    961     }
    962 }