View Javadoc

1   /*
2    * Copyright 2008 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 org.opensaml.saml2.binding.security;
18  
19  import org.opensaml.common.SAMLObject;
20  import org.opensaml.common.SignableSAMLObject;
21  import org.opensaml.common.binding.SAMLMessageContext;
22  import org.opensaml.common.xml.SAMLConstants;
23  import org.opensaml.saml2.core.AuthnRequest;
24  import org.opensaml.saml2.metadata.SPSSODescriptor;
25  import org.opensaml.saml2.metadata.provider.MetadataProvider;
26  import org.opensaml.saml2.metadata.provider.MetadataProviderException;
27  import org.opensaml.ws.message.MessageContext;
28  import org.opensaml.ws.security.SecurityPolicyException;
29  import org.opensaml.ws.security.SecurityPolicyRule;
30  import org.opensaml.ws.transport.http.HTTPInTransport;
31  import org.opensaml.xml.util.DatatypeHelper;
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  
35  /**
36   * Security policy rule implementation that enforces the AuthnRequestsSigned flag of 
37   * SAML 2 metadata element @{link {@link SPSSODescriptor}.
38   */
39  public class SAML2AuthnRequestsSignedRule implements SecurityPolicyRule {
40      
41      /** Logger. */
42      private final Logger log = LoggerFactory.getLogger(SAML2AuthnRequestsSignedRule.class);
43  
44      /** {@inheritDoc} */
45      public void evaluate(MessageContext messageContext) throws SecurityPolicyException {
46          if (!(messageContext instanceof SAMLMessageContext)) {
47              log.debug("Invalid message context type, this policy rule only supports SAMLMessageContext");
48              return;
49          }
50          SAMLMessageContext samlMsgCtx = (SAMLMessageContext) messageContext;
51          
52          SAMLObject samlMessage = samlMsgCtx.getInboundSAMLMessage();
53          if (! (samlMessage instanceof AuthnRequest) ) {
54              log.debug("Inbound message is not an instance of AuthnRequest, skipping evaluation...");
55              return;
56          }
57          
58          String messageIssuer = samlMsgCtx.getInboundMessageIssuer();
59          if (DatatypeHelper.isEmpty(messageIssuer)) {
60              log.warn("Inbound message issuer was empty, unable to evaluate rule");
61              return;
62          }
63          
64          MetadataProvider metadataProvider = samlMsgCtx.getMetadataProvider();
65          if (metadataProvider == null) {
66              log.warn("Message context did not contain a metadata provider, unable to evaluate rule");
67              return;
68          }
69          
70          SPSSODescriptor spssoRole;
71          try {
72              spssoRole = (SPSSODescriptor) metadataProvider
73                  .getRole(messageIssuer, SPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS);
74          } catch (MetadataProviderException e) {
75              log.warn("Error resolving SPSSODescriptor metadata for entityID '{}': {}", messageIssuer, e.getMessage());
76              throw new SecurityPolicyException("Error resolving metadata for entity ID", e);
77          }
78          
79          if (spssoRole == null) {
80              log.warn("SPSSODescriptor role metadata for entityID '{}' could not be resolved", messageIssuer);
81              return;
82          }
83          
84          if (spssoRole.isAuthnRequestsSigned() == Boolean.TRUE) {
85              if (! isMessageSigned(samlMsgCtx)) {
86                  log.error("SPSSODescriptor for entity ID '{}' indicates AuthnRequests must be signed, "
87                          + "but inbound message was not signed", messageIssuer);
88                  throw new SecurityPolicyException("Inbound AuthnRequest was required to be signed but was not");
89              }
90          } else {
91              log.debug("SPSSODescriptor for entity ID '{}' does not require AuthnRequests to be signed", messageIssuer);
92          }
93  
94      }
95      
96      /**
97       * Determine whether the inbound message is signed.
98       * 
99       * @param messageContext the message context being evaluated
100      * @return true if the inbound message is signed, otherwise false
101      */
102     protected boolean isMessageSigned(SAMLMessageContext messageContext) {
103         // TODO this really should be determined by the decoders and supplied to the rule
104         // in some fashion, to handle binding-specific signature mechanisms. See JIRA issue JOWS-4.
105         //
106         // For now evaluate here inline for XML Signature and HTTP-Redirect and HTTP-Post-SimpleSign.
107         
108         SAMLObject samlMessage = messageContext.getInboundSAMLMessage();
109         if (samlMessage instanceof SignableSAMLObject) {
110             SignableSAMLObject signableMessage = (SignableSAMLObject) samlMessage;
111             if (signableMessage.isSigned()) {
112                 return true;
113             }
114         }
115         
116         // This handles HTTP-Redirect and HTTP-POST-SimpleSign bindings.
117         HTTPInTransport inTransport = (HTTPInTransport) messageContext.getInboundMessageTransport();
118         String sigParam = inTransport.getParameterValue("Signature");
119         return !DatatypeHelper.isEmpty(sigParam);
120     }
121 
122 }