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.wss4j.dom.validate;
21  
22  import java.time.Instant;
23  import java.util.List;
24  
25  import org.apache.wss4j.common.cache.ReplayCache;
26  import org.apache.wss4j.common.ext.WSSecurityException;
27  import org.apache.wss4j.common.saml.OpenSAMLUtil;
28  import org.apache.wss4j.common.saml.SAMLKeyInfo;
29  import org.apache.wss4j.common.saml.SamlAssertionWrapper;
30  import org.apache.wss4j.common.saml.builder.SAML1Constants;
31  import org.apache.wss4j.common.saml.builder.SAML2Constants;
32  import org.apache.wss4j.dom.handler.RequestData;
33  import org.opensaml.saml.common.SAMLVersion;
34  
35  /**
36   * This class validates a SAML Assertion, which is wrapped in an "SamlAssertionWrapper" instance.
37   * It assumes that the SamlAssertionWrapper instance has already verified the signature on the
38   * assertion (done by the SAMLTokenProcessor). It verifies trust in the signature, and also
39   * checks that the Subject contains a KeyInfo (and processes it) for the holder-of-key case,
40   * and verifies that the Assertion is signed as well for holder-of-key.
41   */
42  public class SamlAssertionValidator extends SignatureTrustValidator {
43  
44      private static final org.slf4j.Logger LOG =
45          org.slf4j.LoggerFactory.getLogger(SamlAssertionValidator.class);
46  
47      /**
48       * The time in seconds in the future within which the NotBefore time of an incoming
49       * Assertion is valid. The default is 60 seconds.
50       */
51      private int futureTTL = 60;
52  
53      /**
54       * The time in seconds within which a SAML Assertion is valid, if it does not contain
55       * a NotOnOrAfter Condition. The default is 30 minutes.
56       */
57      private int ttl = 60 * 30;
58  
59      /**
60       * Whether to validate the signature of the Assertion (if it exists) against the
61       * relevant profile. Default is true.
62       */
63      private boolean validateSignatureAgainstProfile = true;
64  
65      /**
66       * If this is set, then the value must appear as one of the Subject Confirmation Methods
67       */
68      private String requiredSubjectConfirmationMethod;
69  
70      /**
71       * If this is set, at least one of the standard Subject Confirmation Methods *must*
72       * be present in the assertion (Bearer / SenderVouches / HolderOfKey).
73       */
74      private boolean requireStandardSubjectConfirmationMethod = true;
75  
76      /**
77       * If this is set, an Assertion with a Bearer SubjectConfirmation Method must be
78       * signed
79       */
80      private boolean requireBearerSignature = true;
81  
82      /**
83       * Set the time in seconds in the future within which the NotBefore time of an incoming
84       * Assertion is valid. The default is 60 seconds.
85       */
86      public void setFutureTTL(int newFutureTTL) {
87          futureTTL = newFutureTTL;
88      }
89  
90      /**
91       * Validate the credential argument. It must contain a non-null SamlAssertionWrapper.
92       * A Crypto and a CallbackHandler implementation is also required to be set.
93       *
94       * @param credential the Credential to be validated
95       * @param data the RequestData associated with the request
96       * @throws WSSecurityException on a failed validation
97       */
98      public Credential validate(Credential credential, RequestData data) throws WSSecurityException {
99          if (credential == null || credential.getSamlAssertion() == null) {
100             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "noCredential");
101         }
102         SamlAssertionWrapper samlAssertion = credential.getSamlAssertion();
103 
104         // Check the Subject Confirmation requirements
105         verifySubjectConfirmationMethod(samlAssertion);
106 
107         // Check conditions
108         checkConditions(samlAssertion, data.getAudienceRestrictions());
109 
110         // Check the AuthnStatements of the assertion (if any)
111         checkAuthnStatements(samlAssertion);
112 
113         // Check OneTimeUse Condition
114         checkOneTimeUse(samlAssertion, data);
115 
116         // Validate the assertion against schemas/profiles
117         validateAssertion(samlAssertion);
118 
119         // Verify trust on the signature
120         if (samlAssertion.isSigned()) {
121             verifySignedAssertion(samlAssertion, data);
122         }
123         return credential;
124     }
125 
126     /**
127      * Check the Subject Confirmation method requirements
128      */
129     protected void verifySubjectConfirmationMethod(
130         SamlAssertionWrapper samlAssertion
131     ) throws WSSecurityException {
132 
133         List<String> methods = samlAssertion.getConfirmationMethods();
134         if (methods == null || methods.isEmpty()) {
135             if (requiredSubjectConfirmationMethod != null) {
136                 LOG.warn("A required subject confirmation method was not present");
137                 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
138                                           "invalidSAMLsecurity");
139             } else if (requireStandardSubjectConfirmationMethod) {
140                 LOG.warn("A standard subject confirmation method was not present");
141                 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
142                                           "invalidSAMLsecurity");
143             }
144         }
145 
146         boolean signed = samlAssertion.isSigned();
147         boolean requiredMethodFound = false;
148         boolean standardMethodFound = false;
149         if (methods != null) {
150             for (String method : methods) {
151                 if (OpenSAMLUtil.isMethodHolderOfKey(method)) {
152                     if (samlAssertion.getSubjectKeyInfo() == null) {
153                         LOG.warn("There is no Subject KeyInfo to match the holder-of-key subject conf method");
154                         throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "noKeyInSAMLToken");
155                     }
156 
157                     // The assertion must have been signed for HOK
158                     if (!signed) {
159                         LOG.warn("A holder-of-key assertion must be signed");
160                         throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
161                     }
162                     standardMethodFound = true;
163                 }
164 
165                 if (method != null) {
166                     if (method.equals(requiredSubjectConfirmationMethod)) {
167                         requiredMethodFound = true;
168                     }
169                     if (SAML2Constants.CONF_BEARER.equals(method)
170                         || SAML1Constants.CONF_BEARER.equals(method)) {
171                         standardMethodFound = true;
172                         if (requireBearerSignature && !signed) {
173                             LOG.warn("A Bearer Assertion was not signed");
174                             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
175                                                           "invalidSAMLsecurity");
176                         }
177                     } else if (SAML2Constants.CONF_SENDER_VOUCHES.equals(method)
178                         || SAML1Constants.CONF_SENDER_VOUCHES.equals(method)) {
179                         standardMethodFound = true;
180                     }
181                 }
182             }
183         }
184 
185         if (!requiredMethodFound && requiredSubjectConfirmationMethod != null) {
186             LOG.warn("A required subject confirmation method was not present");
187             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
188                                           "invalidSAMLsecurity");
189         }
190 
191         if (!standardMethodFound && requireStandardSubjectConfirmationMethod) {
192             LOG.warn("A standard subject confirmation method was not present");
193             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
194                                       "invalidSAMLsecurity");
195         }
196     }
197 
198     /**
199      * Verify trust in the signature of a signed Assertion. This method is separate so that
200      * the user can override if if they want.
201      * @param samlAssertion The signed Assertion
202      * @param data The RequestData context
203      * @return A Credential instance
204      * @throws WSSecurityException
205      */
206     protected Credential verifySignedAssertion(
207         SamlAssertionWrapper samlAssertion,
208         RequestData data
209     ) throws WSSecurityException {
210         Credential trustCredential = new Credential();
211         SAMLKeyInfo samlKeyInfo = samlAssertion.getSignatureKeyInfo();
212         trustCredential.setPublicKey(samlKeyInfo.getPublicKey());
213         trustCredential.setCertificates(samlKeyInfo.getCerts());
214         return super.validate(trustCredential, data);
215     }
216 
217     /**
218      * Check the Conditions of the Assertion.
219      */
220     protected void checkConditions(
221         SamlAssertionWrapper samlAssertion, List<String> audienceRestrictions
222     ) throws WSSecurityException {
223         checkConditions(samlAssertion);
224         samlAssertion.checkAudienceRestrictions(audienceRestrictions);
225     }
226 
227     /**
228      * Check the Conditions of the Assertion.
229      */
230     protected void checkConditions(SamlAssertionWrapper samlAssertion) throws WSSecurityException {
231         samlAssertion.checkConditions(futureTTL);
232         samlAssertion.checkIssueInstant(futureTTL, ttl);
233     }
234 
235     /**
236      * Check the AuthnStatements of the Assertion (if any)
237      */
238     protected void checkAuthnStatements(SamlAssertionWrapper samlAssertion) throws WSSecurityException {
239         samlAssertion.checkAuthnStatements(futureTTL);
240     }
241 
242     /**
243      * Check the "OneTimeUse" Condition of the Assertion. If this is set then the Assertion
244      * is cached (if a cache is defined), and must not have been previously cached
245      */
246     protected void checkOneTimeUse(
247         SamlAssertionWrapper samlAssertion, RequestData data
248     ) throws WSSecurityException {
249         if (samlAssertion.getSamlVersion().equals(SAMLVersion.VERSION_20)
250             && samlAssertion.getSaml2().getConditions() != null
251             && samlAssertion.getSaml2().getConditions().getOneTimeUse() != null
252             && data.getSamlOneTimeUseReplayCache() != null) {
253             String identifier = samlAssertion.getId();
254 
255             ReplayCache replayCache = data.getSamlOneTimeUseReplayCache();  //NOPMD
256             if (replayCache.contains(identifier)) {
257                 throw new WSSecurityException(
258                     WSSecurityException.ErrorCode.INVALID_SECURITY,
259                     "badSamlToken",
260                     new Object[] {"A replay attack has been detected"});
261             }
262 
263             Instant expires = samlAssertion.getSaml2().getConditions().getNotOnOrAfter();
264             if (expires != null) {
265                 replayCache.add(identifier, expires);
266             } else {
267                 replayCache.add(identifier);
268             }
269 
270             replayCache.add(identifier);
271         }
272     }
273 
274     /**
275      * Validate the samlAssertion against schemas/profiles
276      */
277     protected void validateAssertion(SamlAssertionWrapper samlAssertion) throws WSSecurityException {
278         if (validateSignatureAgainstProfile) {
279             samlAssertion.validateSignatureAgainstProfile();
280         }
281     }
282 
283     /**
284      * Whether to validate the signature of the Assertion (if it exists) against the
285      * relevant profile. Default is true.
286      */
287     public boolean isValidateSignatureAgainstProfile() {
288         return validateSignatureAgainstProfile;
289     }
290 
291     /**
292      * Whether to validate the signature of the Assertion (if it exists) against the
293      * relevant profile. Default is true.
294      */
295     public void setValidateSignatureAgainstProfile(boolean validateSignatureAgainstProfile) {
296         this.validateSignatureAgainstProfile = validateSignatureAgainstProfile;
297     }
298 
299     public String getRequiredSubjectConfirmationMethod() {
300         return requiredSubjectConfirmationMethod;
301     }
302 
303     public void setRequiredSubjectConfirmationMethod(String requiredSubjectConfirmationMethod) {
304         this.requiredSubjectConfirmationMethod = requiredSubjectConfirmationMethod;
305     }
306 
307     public boolean isRequireStandardSubjectConfirmationMethod() {
308         return requireStandardSubjectConfirmationMethod;
309     }
310 
311     public void setRequireStandardSubjectConfirmationMethod(boolean requireStandardSubjectConfirmationMethod) {
312         this.requireStandardSubjectConfirmationMethod = requireStandardSubjectConfirmationMethod;
313     }
314 
315     public boolean isRequireBearerSignature() {
316         return requireBearerSignature;
317     }
318 
319     public void setRequireBearerSignature(boolean requireBearerSignature) {
320         this.requireBearerSignature = requireBearerSignature;
321     }
322 
323     public int getTtl() {
324         return ttl;
325     }
326 
327     public void setTtl(int ttl) {
328         this.ttl = ttl;
329     }
330 
331 }