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.common.binding.security;
18  
19  import java.util.List;
20  
21  import javax.servlet.http.HttpServletRequest;
22  
23  import org.opensaml.common.binding.SAMLMessageContext;
24  import org.opensaml.security.MetadataCriteria;
25  import org.opensaml.ws.message.MessageContext;
26  import org.opensaml.ws.security.SecurityPolicyException;
27  import org.opensaml.ws.security.SecurityPolicyRule;
28  import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
29  import org.opensaml.xml.security.CriteriaSet;
30  import org.opensaml.xml.security.SecurityException;
31  import org.opensaml.xml.security.credential.Credential;
32  import org.opensaml.xml.security.credential.UsageType;
33  import org.opensaml.xml.security.criteria.EntityIDCriteria;
34  import org.opensaml.xml.security.criteria.UsageCriteria;
35  import org.opensaml.xml.signature.SignatureTrustEngine;
36  import org.opensaml.xml.util.Base64;
37  import org.opensaml.xml.util.DatatypeHelper;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  /**
42   * Base class for security rules which verify simple "blob" signatures computed over some components of a request.
43   */
44  public abstract class BaseSAMLSimpleSignatureSecurityPolicyRule implements SecurityPolicyRule {
45  
46      /** Logger. */
47      private final Logger log = LoggerFactory.getLogger(BaseSAMLSimpleSignatureSecurityPolicyRule.class);
48  
49      /** Signature trust engine used to validate raw signatures. */
50      private SignatureTrustEngine trustEngine;
51  
52      /**
53       * Constructor.
54       * 
55       * @param engine the signature trust engine to use for signature validataion
56       */
57      protected BaseSAMLSimpleSignatureSecurityPolicyRule(SignatureTrustEngine engine) {
58          trustEngine = engine;
59      }
60  
61      /** {@inheritDoc} */
62      public void evaluate(MessageContext messageContext) throws SecurityPolicyException {
63          log.debug("Evaluating simple signature rule of type: {}", getClass().getName());
64          if (!(messageContext instanceof SAMLMessageContext)) {
65              log.debug("Invalid message context type, this policy rule only supports SAMLMessageContext");
66              return;
67          }
68  
69          if (!(messageContext.getInboundMessageTransport() instanceof HttpServletRequestAdapter)) {
70              log.debug("Invalid inbound message transport type, this rule only supports HttpServletRequestAdapter");
71              return;
72          }
73  
74          SAMLMessageContext samlMsgCtx = (SAMLMessageContext) messageContext;
75          HttpServletRequestAdapter requestAdapter = (HttpServletRequestAdapter) messageContext
76                  .getInboundMessageTransport();
77          HttpServletRequest request = requestAdapter.getWrappedRequest();
78  
79          if (!ruleHandles(request, samlMsgCtx)) {
80              log.debug("Rule can not handle this request, skipping processing");
81              return;
82          }
83  
84          byte[] signature = getSignature(request);
85          if (signature == null || signature.length == 0) {
86              log.debug("HTTP request was not signed via simple signature mechanism, skipping");
87              return;
88          }
89  
90          String sigAlg = getSignatureAlgorithm(request);
91          if (DatatypeHelper.isEmpty(sigAlg)) {
92              log.warn("Signature algorithm could not be extracted from request, can not validate simple signature");
93              return;
94          }
95  
96          byte[] signedContent = getSignedContent(request);
97          if (signedContent == null || signedContent.length == 0) {
98              log.warn("Signed content could not be extracted from HTTP request, can not validate");
99              return;
100         }
101 
102         doEvaluate(signature, signedContent, sigAlg, request, samlMsgCtx);
103     }
104 
105     /**
106      * Evaluate the simple signature based on information in the request and/or message context.
107      * 
108      * @param signature the signature value
109      * @param signedContent the content that was signed
110      * @param algorithmURI the signature algorithm URI which was used to sign the content
111      * @param request the HTTP servlet request being processed
112      * @param samlMsgCtx the SAML message context being processed
113      * 
114      * @throws SecurityPolicyException thrown if there are errors during the signature validation process
115      * 
116      */
117     private void doEvaluate(byte[] signature, byte[] signedContent, String algorithmURI, HttpServletRequest request,
118             SAMLMessageContext samlMsgCtx) throws SecurityPolicyException {
119 
120         List<Credential> candidateCredentials = getRequestCredentials(request, samlMsgCtx);
121 
122         String contextIssuer = samlMsgCtx.getInboundMessageIssuer();
123 
124         if (contextIssuer != null) {
125             log.debug("Attempting to validate SAML protocol message simple signature using context issuer: {}",
126                     contextIssuer);
127             CriteriaSet criteriaSet = buildCriteriaSet(contextIssuer, samlMsgCtx);
128             if (validateSignature(signature, signedContent, algorithmURI, criteriaSet, candidateCredentials)) {
129                 log.info("Validation of request simple signature succeeded");
130                 if (!samlMsgCtx.isInboundSAMLMessageAuthenticated()) {
131                     log.info("Authentication via request simple signature succeeded for context issuer entity ID {}",
132                             contextIssuer);
133                     samlMsgCtx.setInboundSAMLMessageAuthenticated(true);
134                 }
135                 return;
136             } else {
137                 log.warn("Validation of request simple signature failed for context issuer: {}", contextIssuer);
138                 throw new SecurityPolicyException("Validation of request simple signature failed for context issuer");
139             }
140         }
141             
142         String derivedIssuer = deriveSignerEntityID(samlMsgCtx);
143         if (derivedIssuer != null) {
144             log.debug("Attempting to validate SAML protocol message simple signature using derived issuer: {}",
145                     derivedIssuer);
146             CriteriaSet criteriaSet = buildCriteriaSet(derivedIssuer, samlMsgCtx);
147             if (validateSignature(signature, signedContent, algorithmURI, criteriaSet, candidateCredentials)) {
148                 log.info("Validation of request simple signature succeeded");
149                 if (!samlMsgCtx.isInboundSAMLMessageAuthenticated()) {
150                     log.info("Authentication via request simple signature succeeded for derived issuer {}",
151                             derivedIssuer);
152                     samlMsgCtx.setInboundMessageIssuer(derivedIssuer);
153                     samlMsgCtx.setInboundSAMLMessageAuthenticated(true);
154                 }
155                 return;
156             } else {
157                 log.warn("Validation of request simple signature failed for derived issuer: {}", derivedIssuer);
158                 throw new SecurityPolicyException("Validation of request simple signature failed for derived issuer");
159             }
160         }
161         
162         log.warn("Neither context nor derived issuer available, can not attempt SAML simple signature validation");
163         throw new SecurityPolicyException("No message issuer available, can not attempt simple signature validation");
164     }
165 
166     /**
167      * Validate the simple signature.
168      * 
169      * @param signature the signature value
170      * @param signedContent the content that was signed
171      * @param algorithmURI the signature algorithm URI which was used to sign the content
172      * @param criteriaSet criteria used to describe and/or resolve the information which serves as the basis for trust
173      *            evaluation
174      * @param candidateCredentials the request-derived candidate credential(s) containing the validation key for the
175      *            signature (optional)
176      * @return true if signature can be verified successfully, false otherwise
177      * 
178      * @throws SecurityPolicyException thrown if there are errors during the signature validation process
179      * 
180      */
181     protected boolean validateSignature(byte[] signature, byte[] signedContent, String algorithmURI,
182             CriteriaSet criteriaSet, List<Credential> candidateCredentials) throws SecurityPolicyException {
183 
184         SignatureTrustEngine engine = getTrustEngine();
185 
186         // Some bindings allow candidate signing credentials to be supplied (e.g. via ds:KeyInfo), some do not.
187         // So have 2 slightly different cases.
188         try {
189             if (candidateCredentials == null || candidateCredentials.isEmpty()) {
190                 if (engine.validate(signature, signedContent, algorithmURI, criteriaSet, null)) {
191                     log.debug("Simple signature validation (with no request-derived credentials) was successful");
192                     return true;
193                 } else {
194                     log.warn("Simple signature validation (with no request-derived credentials) failed");
195                     return false;
196                 }
197             } else {
198                 for (Credential cred : candidateCredentials) {
199                     if (engine.validate(signature, signedContent, algorithmURI, criteriaSet, cred)) {
200                         log.debug("Simple signature validation succeeded with a request-derived credential");
201                         return true;
202                     }
203                 }
204                 log.warn("Signature validation using request-derived credentials failed");
205                 return false;
206             }
207         } catch (SecurityException e) {
208             log.warn("There was an error evaluating the request's simple signature using the trust engine", e);
209             throw new SecurityPolicyException("Error during trust engine evaluation of the simple signature", e);
210         }
211     }
212 
213     /**
214      * Extract any candidate validation credentials from the request and/or message context.
215      * 
216      * Some bindings allow validataion keys for the simple signature to be supplied, and others do not.
217      * 
218      * @param request the HTTP servlet request being processed
219      * @param samlContext the SAML message context being processed
220      * @return a list of candidate validation credentials in the request, or null if none were present
221      * @throws SecurityPolicyException thrown if there is an error during request processing
222      */
223     protected List<Credential> getRequestCredentials(HttpServletRequest request, SAMLMessageContext samlContext)
224             throws SecurityPolicyException {
225         // This will be specific to the binding and message types, so no default.
226         return null;
227     }
228 
229     /**
230      * Gets the engine used to validate the signature.
231      * 
232      * @return engine engine used to validate the signature
233      */
234     protected SignatureTrustEngine getTrustEngine() {
235         return trustEngine;
236     }
237 
238     /**
239      * Extract the signature value from the request, in the form suitable for input into
240      * {@link SignatureTrustEngine#validate(byte[], byte[], String, CriteriaSet, Credential)}.
241      * 
242      * Defaults to the Base64-decoded value of the HTTP request parameter named <code>Signature</code>.
243      * 
244      * @param request the HTTP servlet request
245      * @return the signature value
246      * @throws SecurityPolicyException thrown if there is an error during request processing
247      */
248     protected byte[] getSignature(HttpServletRequest request) throws SecurityPolicyException {
249         String signature = request.getParameter("Signature");
250         if (DatatypeHelper.isEmpty(signature)) {
251             return null;
252         }
253         return Base64.decode(signature);
254     }
255 
256     /**
257      * Extract the signature algorithm URI value from the request.
258      * 
259      * Defaults to the HTTP request parameter named <code>SigAlg</code>.
260      * 
261      * @param request the HTTP servlet request
262      * @return the signature algorithm URI value
263      * @throws SecurityPolicyException thrown if there is an error during request processing
264      */
265     protected String getSignatureAlgorithm(HttpServletRequest request) throws SecurityPolicyException {
266         return request.getParameter("SigAlg");
267     }
268 
269     /**
270      * Derive the signer's entity ID from the message context.
271      * 
272      * This is implementation-specific and there is no default. This is primarily an extension point for subclasses.
273      * 
274      * @param samlContext the SAML message context being processed
275      * @return the signer's derived entity ID
276      * @throws SecurityPolicyException thrown if there is an error during request processing
277      */
278     protected String deriveSignerEntityID(SAMLMessageContext samlContext) throws SecurityPolicyException {
279         // No default
280         return null;
281     }
282 
283     /**
284      * Build a criteria set suitable for input to the trust engine.
285      * 
286      * @param entityID the candidate issuer entity ID which is being evaluated
287      * @param samlContext the message context which is being evaluated
288      * @return a newly constructly set of criteria suitable for the configured trust engine
289      * @throws SecurityPolicyException thrown if criteria set can not be constructed
290      */
291     protected CriteriaSet buildCriteriaSet(String entityID, SAMLMessageContext samlContext)
292             throws SecurityPolicyException {
293 
294         CriteriaSet criteriaSet = new CriteriaSet();
295         if (!DatatypeHelper.isEmpty(entityID)) {
296             criteriaSet.add(new EntityIDCriteria(entityID));
297         }
298 
299         MetadataCriteria mdCriteria = new MetadataCriteria(samlContext.getPeerEntityRole(), samlContext
300                 .getInboundSAMLProtocol());
301         criteriaSet.add(mdCriteria);
302 
303         criteriaSet.add(new UsageCriteria(UsageType.SIGNING));
304 
305         return criteriaSet;
306     }
307 
308     /**
309      * Get the content over which to validate the signature, in the form suitable for input into
310      * {@link SignatureTrustEngine#validate(byte[], byte[], String, CriteriaSet, Credential)}.
311      * 
312      * @param request the HTTP servlet request being processed
313      * @return the signed content extracted from the request, in the format suitable for input to the trust engine.
314      * @throws SecurityPolicyException thrown if there is an error during request processing
315      */
316     protected abstract byte[] getSignedContent(HttpServletRequest request) throws SecurityPolicyException;
317 
318     /**
319      * Determine whether the rule should handle the request, based on the unwrapped HTTP servlet request and/or message
320      * context.
321      * 
322      * @param request the HTTP servlet request being processed
323      * @param samlMsgCtx the SAML message context being processed
324      * @return true if the rule should attempt to process the request, otherwise false
325      * @throws SecurityPolicyException thrown if there is an error during request processing
326      */
327     protected abstract boolean ruleHandles(HttpServletRequest request, SAMLMessageContext samlMsgCtx)
328             throws SecurityPolicyException;
329 
330 }