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  package org.apache.wss4j.stax.validate;
20  
21  import java.time.Instant;
22  import java.util.List;
23  
24  import org.apache.wss4j.common.cache.ReplayCache;
25  import org.apache.wss4j.common.crypto.Crypto;
26  import org.apache.wss4j.common.ext.WSSecurityException;
27  import org.apache.wss4j.common.saml.OpenSAMLUtil;
28  import org.apache.wss4j.common.saml.SamlAssertionWrapper;
29  import org.apache.wss4j.common.saml.builder.SAML1Constants;
30  import org.apache.wss4j.common.saml.builder.SAML2Constants;
31  import org.apache.wss4j.stax.securityToken.SamlSecurityToken;
32  import org.apache.wss4j.stax.impl.securityToken.SamlSecurityTokenImpl;
33  import org.apache.wss4j.stax.securityToken.WSSecurityTokenConstants;
34  import org.apache.xml.security.stax.securityToken.InboundSecurityToken;
35  import org.opensaml.saml.common.SAMLVersion;
36  
37  public class SamlTokenValidatorImpl extends SignatureTokenValidatorImpl implements SamlTokenValidator {
38  
39      private static final transient org.slf4j.Logger LOG =
40          org.slf4j.LoggerFactory.getLogger(SamlTokenValidatorImpl.class);
41  
42      /**
43       * The time in seconds in the future within which the NotBefore time of an incoming
44       * Assertion is valid. The default is 60 seconds.
45       */
46      private int futureTTL = 60;
47  
48      /**
49       * The time in seconds within which a SAML Assertion is valid, if it does not contain
50       * a NotOnOrAfter Condition. The default is 30 minutes.
51       */
52      private int ttl = 60 * 30;
53  
54      /**
55       * Whether to validate the signature of the Assertion (if it exists) against the
56       * relevant profile. Default is true.
57       */
58      private boolean validateSignatureAgainstProfile = true;
59  
60      /**
61       * If this is set, then the value must appear as one of the Subject Confirmation Methods
62       */
63      private String requiredSubjectConfirmationMethod;
64  
65      /**
66       * If this is set, at least one of the standard Subject Confirmation Methods *must*
67       * be present in the assertion (Bearer / SenderVouches / HolderOfKey).
68       */
69      private boolean requireStandardSubjectConfirmationMethod = true;
70  
71      /**
72       * If this is set, an Assertion with a Bearer SubjectConfirmation Method must be
73       * signed
74       */
75      private boolean requireBearerSignature = true;
76  
77      /**
78       * Set the time in seconds in the future within which the NotBefore time of an incoming
79       * Assertion is valid. The default is 60 seconds.
80       */
81      public void setFutureTTL(int newFutureTTL) {
82          futureTTL = newFutureTTL;
83      }
84  
85      /**
86       * Whether to validate the signature of the Assertion (if it exists) against the
87       * relevant profile. Default is true.
88       */
89      public boolean isValidateSignatureAgainstProfile() {
90          return validateSignatureAgainstProfile;
91      }
92  
93      /**
94       * Whether to validate the signature of the Assertion (if it exists) against the
95       * relevant profile. Default is true.
96       */
97      public void setValidateSignatureAgainstProfile(boolean validateSignatureAgainstProfile) {
98          this.validateSignatureAgainstProfile = validateSignatureAgainstProfile;
99      }
100 
101     public String getRequiredSubjectConfirmationMethod() {
102         return requiredSubjectConfirmationMethod;
103     }
104 
105     public void setRequiredSubjectConfirmationMethod(String requiredSubjectConfirmationMethod) {
106         this.requiredSubjectConfirmationMethod = requiredSubjectConfirmationMethod;
107     }
108 
109     @Override
110     public <T extends SamlSecurityToken & InboundSecurityToken> T validate(final SamlAssertionWrapper samlAssertionWrapper,
111                                                  final InboundSecurityToken subjectSecurityToken,
112                                                  final TokenContext tokenContext) throws WSSecurityException {
113         // Check conditions
114         checkConditions(samlAssertionWrapper,
115                         tokenContext.getWssSecurityProperties().getAudienceRestrictions());
116 
117         // Check the AuthnStatements of the assertion (if any)
118         checkAuthnStatements(samlAssertionWrapper);
119 
120         // Check the Subject Confirmation requirements
121         verifySubjectConfirmationMethod(samlAssertionWrapper);
122 
123         // Check OneTimeUse Condition
124         checkOneTimeUse(samlAssertionWrapper,
125                         tokenContext.getWssSecurityProperties().getSamlOneTimeUseReplayCache());
126 
127         // Validate the assertion against schemas/profiles
128         validateAssertion(samlAssertionWrapper);
129 
130         Crypto sigVerCrypto = null;
131         if (samlAssertionWrapper.isSigned()) {
132             sigVerCrypto = tokenContext.getWssSecurityProperties().getSignatureVerificationCrypto();
133         }
134         SamlSecurityTokenImpl securityToken = new SamlSecurityTokenImpl(
135                 samlAssertionWrapper, subjectSecurityToken,
136                 tokenContext.getWsSecurityContext(),
137                 sigVerCrypto,
138                 WSSecurityTokenConstants.KeyIdentifier_NoKeyInfo,
139                 tokenContext.getWssSecurityProperties());
140 
141         securityToken.setElementPath(tokenContext.getElementPath());
142         securityToken.setXMLSecEvent(tokenContext.getFirstXMLSecEvent());
143         @SuppressWarnings("unchecked")
144         T token = (T)securityToken;
145         return token;
146     }
147 
148     /**
149      * Check the Subject Confirmation method requirements
150      */
151     protected void verifySubjectConfirmationMethod(
152         SamlAssertionWrapper samlAssertion
153     ) throws WSSecurityException {
154 
155         List<String> methods = samlAssertion.getConfirmationMethods();
156         if (methods == null || methods.isEmpty()) {
157             if (requiredSubjectConfirmationMethod != null) {
158                 LOG.warn("A required subject confirmation method was not present");
159                 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
160                                           "invalidSAMLsecurity");
161             } else if (requireStandardSubjectConfirmationMethod) {
162                 LOG.warn("A standard subject confirmation method was not present");
163                 throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
164                                           "invalidSAMLsecurity");
165             }
166         }
167 
168         boolean signed = samlAssertion.isSigned();
169         boolean requiredMethodFound = false;
170         boolean standardMethodFound = false;
171         if (methods != null) {
172             for (String method : methods) {
173                 // The assertion must have been signed for HOK
174                 if (OpenSAMLUtil.isMethodHolderOfKey(method)) {
175                     if (!signed) {
176                         LOG.warn("A holder-of-key assertion must be signed");
177                         throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
178                                                       "invalidSAMLsecurity");
179                     }
180                     standardMethodFound = true;
181                 }
182 
183                 if (method != null) {
184                     if (method.equals(requiredSubjectConfirmationMethod)) {
185                         requiredMethodFound = true;
186                     }
187                     if (SAML2Constants.CONF_BEARER.equals(method)
188                         || SAML1Constants.CONF_BEARER.equals(method)) {
189                         standardMethodFound = true;
190                         if (requireBearerSignature && !signed) {
191                             LOG.warn("A Bearer Assertion was not signed");
192                             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
193                                                           "invalidSAMLsecurity");
194                         }
195                     } else if (SAML2Constants.CONF_SENDER_VOUCHES.equals(method)
196                         || SAML1Constants.CONF_SENDER_VOUCHES.equals(method)) {
197                         standardMethodFound = true;
198                     }
199                 }
200             }
201         }
202 
203         if (!requiredMethodFound && requiredSubjectConfirmationMethod != null) {
204             LOG.warn("A required subject confirmation method was not present");
205             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
206                                           "invalidSAMLsecurity");
207         }
208 
209         if (!standardMethodFound && requireStandardSubjectConfirmationMethod) {
210             LOG.warn("A standard subject confirmation method was not present");
211             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
212                                       "invalidSAMLsecurity");
213         }
214     }
215 
216     /**
217      * Check the Conditions of the Assertion.
218      */
219     protected void checkConditions(
220         SamlAssertionWrapper samlAssertion, List<String> audienceRestrictions
221     ) throws WSSecurityException {
222         checkConditions(samlAssertion);
223         samlAssertion.checkAudienceRestrictions(audienceRestrictions);
224     }
225 
226     /**
227      * Check the Conditions of the Assertion.
228      */
229     protected void checkConditions(SamlAssertionWrapper samlAssertion) throws WSSecurityException {
230         samlAssertion.checkConditions(futureTTL);
231         samlAssertion.checkIssueInstant(futureTTL, ttl);
232     }
233 
234     /**
235      * Check the AuthnStatements of the Assertion (if any)
236      */
237     protected void checkAuthnStatements(SamlAssertionWrapper samlAssertion) throws WSSecurityException {
238         samlAssertion.checkAuthnStatements(futureTTL);
239     }
240 
241     /**
242      * Check the "OneTimeUse" Condition of the Assertion. If this is set then the Assertion
243      * is cached (if a cache is defined), and must not have been previously cached
244      */
245     protected void checkOneTimeUse(
246         SamlAssertionWrapper samlAssertion, ReplayCache replayCache
247     ) throws WSSecurityException {
248         if (replayCache != null
249             && samlAssertion.getSamlVersion().equals(SAMLVersion.VERSION_20)
250             && samlAssertion.getSaml2().getConditions() != null
251             && samlAssertion.getSaml2().getConditions().getOneTimeUse() != null) {
252             String identifier = samlAssertion.getId();
253 
254             if (replayCache.contains(identifier)) {
255                 throw new WSSecurityException(
256                     WSSecurityException.ErrorCode.INVALID_SECURITY,
257                     "badSamlToken",
258                     new Object[] {"A replay attack has been detected"});
259             }
260 
261             Instant expires = samlAssertion.getSaml2().getConditions().getNotOnOrAfter();
262             if (expires != null) {
263                 replayCache.add(identifier, expires);
264             } else {
265                 replayCache.add(identifier);
266             }
267         }
268     }
269 
270     /**
271      * Validate the samlAssertion against schemas/profiles
272      */
273     protected void validateAssertion(SamlAssertionWrapper samlAssertion) throws WSSecurityException {
274         if (validateSignatureAgainstProfile) {
275             samlAssertion.validateSignatureAgainstProfile();
276         }
277     }
278 
279     public boolean isRequireStandardSubjectConfirmationMethod() {
280         return requireStandardSubjectConfirmationMethod;
281     }
282 
283     public void setRequireStandardSubjectConfirmationMethod(boolean requireStandardSubjectConfirmationMethod) {
284         this.requireStandardSubjectConfirmationMethod = requireStandardSubjectConfirmationMethod;
285     }
286 
287     public boolean isRequireBearerSignature() {
288         return requireBearerSignature;
289     }
290 
291     public void setRequireBearerSignature(boolean requireBearerSignature) {
292         this.requireBearerSignature = requireBearerSignature;
293     }
294 
295     public int getTtl() {
296         return ttl;
297     }
298 
299     public void setTtl(int ttl) {
300         this.ttl = ttl;
301     }
302 
303 }