View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.ws.security.validate;
21  
22  import java.util.List;
23  
24  import org.apache.ws.security.WSSecurityException;
25  import org.apache.ws.security.handler.RequestData;
26  import org.apache.ws.security.saml.SAMLKeyInfo;
27  import org.apache.ws.security.saml.ext.AssertionWrapper;
28  import org.apache.ws.security.saml.ext.OpenSAMLUtil;
29  import org.joda.time.DateTime;
30  import org.opensaml.common.SAMLVersion;
31  import org.opensaml.xml.validation.ValidationException;
32  import org.opensaml.xml.validation.ValidatorSuite;
33  
34  /**
35   * This class validates a SAML Assertion, which is wrapped in an "AssertionWrapper" instance.
36   * It assumes that the AssertionWrapper instance has already verified the signature on the
37   * assertion (done by the SAMLTokenProcessor). It verifies trust in the signature, and also
38   * checks that the Subject contains a KeyInfo (and processes it) for the holder-of-key case,
39   * and verifies that the Assertion is signed as well for holder-of-key. 
40   */
41  public class SamlAssertionValidator extends SignatureTrustValidator {
42      
43      private static final org.apache.commons.logging.Log LOG = 
44          org.apache.commons.logging.LogFactory.getLog(SamlAssertionValidator.class);
45      
46      /**
47       * The time in seconds in the future within which the NotBefore time of an incoming 
48       * Assertion is valid. The default is 60 seconds.
49       */
50      private int futureTTL = 60;
51      
52      /**
53       * Whether to validate the signature of the Assertion (if it exists) against the 
54       * relevant profile. Default is true.
55       */
56      private boolean validateSignatureAgainstProfile = true;
57      
58      /**
59       * Set the time in seconds in the future within which the NotBefore time of an incoming 
60       * Assertion is valid. The default is 60 seconds.
61       */
62      public void setFutureTTL(int newFutureTTL) {
63          futureTTL = newFutureTTL;
64      }
65      
66      /**
67       * Validate the credential argument. It must contain a non-null AssertionWrapper. 
68       * A Crypto and a CallbackHandler implementation is also required to be set.
69       * 
70       * @param credential the Credential to be validated
71       * @param data the RequestData associated with the request
72       * @throws WSSecurityException on a failed validation
73       */
74      public Credential validate(Credential credential, RequestData data) throws WSSecurityException {
75          if (credential == null || credential.getAssertion() == null) {
76              throw new WSSecurityException(WSSecurityException.FAILURE, "noCredential");
77          }
78          AssertionWrapper assertion = credential.getAssertion();
79          
80          // Check HOK requirements
81          String confirmMethod = null;
82          List<String> methods = assertion.getConfirmationMethods();
83          if (methods != null && methods.size() > 0) {
84              confirmMethod = methods.get(0);
85          }
86          if (OpenSAMLUtil.isMethodHolderOfKey(confirmMethod)) {
87              if (assertion.getSubjectKeyInfo() == null) {
88                  LOG.debug("There is no Subject KeyInfo to match the holder-of-key subject conf method");
89                  throw new WSSecurityException(WSSecurityException.FAILURE, "noKeyInSAMLToken");
90              }
91              // The assertion must have been signed for HOK
92              if (!assertion.isSigned()) {
93                  LOG.debug("A holder-of-key assertion must be signed");
94                  throw new WSSecurityException(WSSecurityException.FAILURE, "invalidSAMLsecurity");
95              }
96          }
97          
98          // Check conditions
99          checkConditions(assertion);
100         
101         // Validate the assertion against schemas/profiles
102         validateAssertion(assertion);
103 
104         // Verify trust on the signature
105         if (assertion.isSigned()) {
106             verifySignedAssertion(assertion, data);
107         }
108         return credential;
109     }
110     
111     /**
112      * Verify trust in the signature of a signed Assertion. This method is separate so that
113      * the user can override if if they want.
114      * @param assertion The signed Assertion
115      * @param data The RequestData context
116      * @return A Credential instance
117      * @throws WSSecurityException
118      */
119     protected Credential verifySignedAssertion(
120         AssertionWrapper assertion,
121         RequestData data
122     ) throws WSSecurityException {
123         Credential trustCredential = new Credential();
124         SAMLKeyInfo samlKeyInfo = assertion.getSignatureKeyInfo();
125         trustCredential.setPublicKey(samlKeyInfo.getPublicKey());
126         trustCredential.setCertificates(samlKeyInfo.getCerts());
127         return super.validate(trustCredential, data);
128     }
129     
130     /**
131      * Check the Conditions of the Assertion.
132      */
133     protected void checkConditions(AssertionWrapper assertion) throws WSSecurityException {
134         DateTime validFrom = null;
135         DateTime validTill = null;
136         if (assertion.getSamlVersion().equals(SAMLVersion.VERSION_20)
137             && assertion.getSaml2().getConditions() != null) {
138             validFrom = assertion.getSaml2().getConditions().getNotBefore();
139             validTill = assertion.getSaml2().getConditions().getNotOnOrAfter();
140         } else if (assertion.getSamlVersion().equals(SAMLVersion.VERSION_11)
141             && assertion.getSaml1().getConditions() != null) {
142             validFrom = assertion.getSaml1().getConditions().getNotBefore();
143             validTill = assertion.getSaml1().getConditions().getNotOnOrAfter();
144         }
145         
146         if (validFrom != null) {
147             DateTime currentTime = new DateTime();
148             currentTime = currentTime.plusSeconds(futureTTL);
149             if (validFrom.isAfter(currentTime)) {
150                 LOG.debug("SAML Token condition (Not Before) not met");
151                 throw new WSSecurityException(WSSecurityException.FAILURE, "invalidSAMLsecurity");
152             }
153         }
154 
155         if (validTill != null && validTill.isBeforeNow()) {
156             LOG.debug("SAML Token condition (Not On Or After) not met");
157             throw new WSSecurityException(WSSecurityException.FAILURE, "invalidSAMLsecurity");
158         }
159     }
160     
161     /**
162      * Validate the assertion against schemas/profiles
163      */
164     protected void validateAssertion(AssertionWrapper assertion) throws WSSecurityException {
165         if (validateSignatureAgainstProfile) {
166             assertion.validateSignatureAgainstProfile();
167         }
168         
169         if (assertion.getSaml1() != null) {
170             ValidatorSuite schemaValidators = 
171                 org.opensaml.Configuration.getValidatorSuite("saml1-schema-validator");
172             ValidatorSuite specValidators = 
173                 org.opensaml.Configuration.getValidatorSuite("saml1-spec-validator");
174             try {
175                 schemaValidators.validate(assertion.getSaml1());
176                 specValidators.validate(assertion.getSaml1());
177             } catch (ValidationException e) {
178                 LOG.debug("Saml Validation error: " + e.getMessage(), e);
179                 throw new WSSecurityException(
180                     WSSecurityException.FAILURE, "invalidSAMLsecurity", null, e
181                 );
182             }
183         } else if (assertion.getSaml2() != null) {
184             ValidatorSuite schemaValidators = 
185                 org.opensaml.Configuration.getValidatorSuite("saml2-core-schema-validator");
186             ValidatorSuite specValidators = 
187                 org.opensaml.Configuration.getValidatorSuite("saml2-core-spec-validator");
188             try {
189                 schemaValidators.validate(assertion.getSaml2());
190                 specValidators.validate(assertion.getSaml2());
191             } catch (ValidationException e) {
192                 LOG.debug("Saml Validation error: " + e.getMessage(), e);
193                 throw new WSSecurityException(
194                     WSSecurityException.FAILURE, "invalidSAMLsecurity", null, e
195                 );
196             }
197         }
198     }
199 
200     /**
201      * Whether to validate the signature of the Assertion (if it exists) against the 
202      * relevant profile. Default is true.
203      */
204     public boolean isValidateSignatureAgainstProfile() {
205         return validateSignatureAgainstProfile;
206     }
207 
208     /**
209      * Whether to validate the signature of the Assertion (if it exists) against the 
210      * relevant profile. Default is true.
211      */
212     public void setValidateSignatureAgainstProfile(boolean validateSignatureAgainstProfile) {
213         this.validateSignatureAgainstProfile = validateSignatureAgainstProfile;
214     }
215     
216 }