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 org.opensaml.saml1.binding.encoding;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  
23  import org.opensaml.Configuration;
24  import org.opensaml.common.SAMLObject;
25  import org.opensaml.common.SignableSAMLObject;
26  import org.opensaml.common.binding.SAMLMessageContext;
27  import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
28  import org.opensaml.saml2.core.Response;
29  import org.opensaml.saml2.metadata.Endpoint;
30  import org.opensaml.util.URLBuilder;
31  import org.opensaml.ws.message.encoder.BaseMessageEncoder;
32  import org.opensaml.ws.message.encoder.MessageEncodingException;
33  import org.opensaml.xml.XMLObjectBuilder;
34  import org.opensaml.xml.io.Marshaller;
35  import org.opensaml.xml.io.MarshallingException;
36  import org.opensaml.xml.security.SecurityException;
37  import org.opensaml.xml.security.SecurityHelper;
38  import org.opensaml.xml.security.credential.Credential;
39  import org.opensaml.xml.signature.Signature;
40  import org.opensaml.xml.signature.SignatureException;
41  import org.opensaml.xml.signature.Signer;
42  import org.opensaml.xml.util.DatatypeHelper;
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  
46  /**
47   * Base class for SAML 1 message encoders.
48   */
49  public abstract class BaseSAML1MessageEncoder extends BaseMessageEncoder implements SAMLMessageEncoder{
50  
51      /** Class logger. */
52      private final Logger log = LoggerFactory.getLogger(BaseSAML1MessageEncoder.class);
53      
54      /** The list of schemes allowed to appear in URLs related to the encoded message. Defaults to 'http' and 'https'. */
55      private List<String> allowedURLSchemes;
56  
57      public BaseSAML1MessageEncoder() {
58          super();
59          setAllowedURLSchemes(new String[] { "http", "https" });
60      }
61  
62      /**
63       * Gets the unmodifiable list of schemes allowed to appear in URLs related to the encoded message.
64       * 
65       * @return list of URL schemes allowed to appear in a message
66       */
67      public List<String> getAllowedURLSchemes() {
68          return allowedURLSchemes;
69      }
70  
71      /**
72       * Sets the list of list of schemes allowed to appear in URLs related to the encoded message. Note, the appearance
73       * of schemes such as 'javascript' may open the system up to attacks (e.g. cross-site scripting attacks).
74       * 
75       * @param schemes URL schemes allowed to appear in a message
76       */
77      public void setAllowedURLSchemes(String[] schemes) {
78          if (schemes == null || schemes.length == 0) {
79              allowedURLSchemes = Collections.emptyList();
80          } else {
81              List<String> temp = new ArrayList<String>();
82              for (String scheme : schemes) {
83                  temp.add(scheme);
84              }
85              allowedURLSchemes = Collections.unmodifiableList(temp);
86          }
87      }
88  
89      /**
90       * Gets the response URL from the relying party endpoint. If the SAML message is a {@link Response} and the relying
91       * party endpoint contains a response location then that location is returned otherwise the normal endpoint location
92       * is returned.
93       * 
94       * @param messageContext current message context
95       * 
96       * @return response URL from the relying party endpoint
97       * 
98       * @throws MessageEncodingException throw if no relying party endpoint is available
99       */
100     protected URLBuilder getEndpointURL(SAMLMessageContext messageContext) throws MessageEncodingException {
101         Endpoint endpoint = messageContext.getPeerEntityEndpoint();
102         if (endpoint == null) {
103             throw new MessageEncodingException("Endpoint for relying party was null.");
104         }
105 
106         URLBuilder urlBuilder;
107         if (messageContext.getOutboundMessage() instanceof Response
108                 && !DatatypeHelper.isEmpty(endpoint.getResponseLocation())) {
109             urlBuilder = new URLBuilder(endpoint.getResponseLocation());
110         } else {
111             if (DatatypeHelper.isEmpty(endpoint.getLocation())) {
112                 throw new MessageEncodingException("Relying party endpoint location was null or empty.");
113             }
114             urlBuilder = new URLBuilder(endpoint.getLocation());
115         }
116         
117         if(!getAllowedURLSchemes().contains(urlBuilder.getScheme())){
118            throw new MessageEncodingException("Relying party endpoint used the untrusted URL scheme " + urlBuilder.getScheme()); 
119         }
120         return urlBuilder;
121     }
122 
123     /**
124      * Signs the given SAML message if it a {@link SignableSAMLObject} and this encoder has signing credentials.
125      * 
126      * @param messageContext current message context
127      * 
128      * @throws MessageEncodingException thrown if there is a problem preparing the signature for signing
129      */
130     @SuppressWarnings("unchecked")
131     protected void signMessage(SAMLMessageContext messageContext) throws MessageEncodingException {
132         SAMLObject outboundMessage = messageContext.getOutboundSAMLMessage();
133         if (outboundMessage instanceof SignableSAMLObject
134                 && messageContext.getOuboundSAMLMessageSigningCredential() != null) {
135             log.debug("Signing outbound SAML message.");
136             SignableSAMLObject signableMessage = (SignableSAMLObject) outboundMessage;
137             Credential signingCredential = messageContext.getOuboundSAMLMessageSigningCredential();
138 
139             XMLObjectBuilder<Signature> signatureBuilder = Configuration.getBuilderFactory().getBuilder(
140                     Signature.DEFAULT_ELEMENT_NAME);
141             Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
142             signature.setSigningCredential(signingCredential);
143 
144             try {
145                 // TODO pull SecurityConfiguration from SAMLMessageContext? needs to be added
146                 // TODO pull binding-specific keyInfoGenName from encoder setting, etc?
147                 SecurityHelper.prepareSignatureParams(signature, signingCredential, null, null);
148             } catch (SecurityException e) {
149                 throw new MessageEncodingException("Error preparing signature for signing", e);
150             }
151 
152             signableMessage.setSignature(signature);
153 
154             try {
155                 Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(signableMessage);
156                 marshaller.marshall(signableMessage);
157                 Signer.signObject(signature);
158             } catch (MarshallingException e) {
159                 log.error("Unable to marshall protocol message in preparation for signing", e);
160                 throw new MessageEncodingException("Unable to marshall protocol message in preparation for signing", e);
161             } catch (SignatureException e) {
162                 log.error("Unable to sign protocol message", e);
163                 throw new MessageEncodingException("Unable to sign protocol message", e);
164             }
165         }
166     }
167 }