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.policy.stax.assertionStates;
20  
21  import org.apache.wss4j.common.ext.WSSecurityException;
22  import org.apache.wss4j.common.saml.SamlAssertionWrapper;
23  import org.apache.wss4j.common.WSSPolicyException;
24  import org.apache.wss4j.policy.model.AbstractSecurityAssertion;
25  import org.apache.wss4j.policy.model.AbstractToken;
26  import org.apache.wss4j.policy.model.IssuedToken;
27  import org.apache.wss4j.policy.stax.PolicyAsserter;
28  import org.apache.wss4j.stax.ext.WSSConstants;
29  import org.apache.wss4j.stax.securityEvent.KerberosTokenSecurityEvent;
30  import org.apache.wss4j.stax.securityEvent.SamlTokenSecurityEvent;
31  import org.apache.xml.security.exceptions.XMLSecurityException;
32  import org.apache.xml.security.stax.securityEvent.SecurityEventConstants;
33  import org.apache.xml.security.stax.securityEvent.TokenSecurityEvent;
34  import org.apache.xml.security.stax.securityToken.SecurityToken;
35  import org.apache.wss4j.stax.securityEvent.IssuedTokenSecurityEvent;
36  import org.apache.wss4j.stax.securityEvent.WSSecurityEventConstants;
37  import org.opensaml.saml.common.SAMLVersion;
38  import org.w3c.dom.Element;
39  import org.w3c.dom.Node;
40  
41  import java.net.URI;
42  import java.security.Key;
43  import java.security.PublicKey;
44  import java.security.cert.X509Certificate;
45  import java.util.List;
46  import java.util.Map;
47  
48  /**
49   * WSP1.3, 5.4.2 IssuedToken Assertion
50   */
51  
52  public class IssuedTokenAssertionState extends TokenAssertionState {
53  
54      private static final String DEFAULT_CLAIMS_NAMESPACE =
55          "http://schemas.xmlsoap.org/ws/2005/05/identity";
56  
57      public IssuedTokenAssertionState(AbstractSecurityAssertion assertion, boolean asserted,
58                                       PolicyAsserter policyAsserter, boolean initiator) {
59          super(assertion, asserted, policyAsserter, initiator);
60      }
61  
62      @Override
63      public SecurityEventConstants.Event[] getSecurityEventType() {
64          return new SecurityEventConstants.Event[]{
65                  WSSecurityEventConstants.KERBEROS_TOKEN,
66                  WSSecurityEventConstants.REL_TOKEN,
67                  WSSecurityEventConstants.SAML_TOKEN,
68                  WSSecurityEventConstants.SECURITY_CONTEXT_TOKEN,
69          };
70      }
71  
72      @Override
73      public boolean assertToken(TokenSecurityEvent<? extends SecurityToken> tokenSecurityEvent,
74                                 AbstractToken abstractToken) throws WSSPolicyException {
75          if (!(tokenSecurityEvent instanceof IssuedTokenSecurityEvent)) {
76              throw new WSSPolicyException("Expected a IssuedTokenSecurityEvent but got " + tokenSecurityEvent.getClass().getName());
77          }
78  
79          IssuedToken issuedToken = (IssuedToken) abstractToken;
80          IssuedTokenSecurityEvent<? extends SecurityToken> issuedTokenSecurityEvent
81              = (IssuedTokenSecurityEvent<? extends SecurityToken>) tokenSecurityEvent;
82          try {
83              if (issuedToken.getIssuerName() != null
84                  && !issuedToken.getIssuerName().equals(issuedTokenSecurityEvent.getIssuerName())) {
85                  setErrorMessage("IssuerName in Policy (" + issuedToken.getIssuerName()
86                      + ") didn't match with the one in the IssuedToken (" + issuedTokenSecurityEvent.getIssuerName() + ")");
87                  getPolicyAsserter().unassertPolicy(getAssertion(), getErrorMessage());
88                  return false;
89              }
90              if (issuedToken.getRequestSecurityTokenTemplate() != null) {
91                  if (issuedTokenSecurityEvent instanceof SamlTokenSecurityEvent) {
92                      SamlTokenSecurityEvent samlTokenSecurityEvent = (SamlTokenSecurityEvent) issuedTokenSecurityEvent;
93                      String errorMsg = checkIssuedTokenTemplate(issuedToken.getRequestSecurityTokenTemplate(), samlTokenSecurityEvent);
94                      if (errorMsg != null) {
95                          setErrorMessage(errorMsg);
96                          getPolicyAsserter().unassertPolicy(getAssertion(), getErrorMessage());
97                          return false;
98                      }
99                  } else if (issuedTokenSecurityEvent instanceof KerberosTokenSecurityEvent) {
100                     KerberosTokenSecurityEvent kerberosTokenSecurityEvent = (KerberosTokenSecurityEvent) issuedTokenSecurityEvent;
101                     String errorMsg = checkIssuedTokenTemplate(issuedToken.getRequestSecurityTokenTemplate(), kerberosTokenSecurityEvent);
102                     if (errorMsg != null) {
103                         setErrorMessage(errorMsg);
104                         getPolicyAsserter().unassertPolicy(getAssertion(), getErrorMessage());
105                         return false;
106                     }
107                 }
108             }
109 
110             Element claims = issuedToken.getClaims();
111             if (claims != null && issuedTokenSecurityEvent instanceof SamlTokenSecurityEvent) {
112                 String errorMsg =
113                     validateClaims((Element) claims, (SamlTokenSecurityEvent)issuedTokenSecurityEvent);
114                 if (errorMsg != null) {
115                     setErrorMessage(errorMsg);
116                     getPolicyAsserter().unassertPolicy(getAssertion(), getErrorMessage());
117                     return false;
118                 }
119             }
120         } catch (XMLSecurityException e) {
121             getPolicyAsserter().unassertPolicy(getAssertion(), getErrorMessage());
122             throw new WSSPolicyException(e.getMessage(), e);
123         }
124 
125         //always return true to prevent false alarm in case additional tokens with the same usage
126         //appears in the message but do not fulfill the policy and are also not needed to fulfil the policy.
127         getPolicyAsserter().assertPolicy(getAssertion());
128         return true;
129     }
130 
131     /**
132      * Check the issued token template against the received assertion
133      */
134     protected String checkIssuedTokenTemplate(Element template, SamlTokenSecurityEvent samlTokenSecurityEvent) throws XMLSecurityException {
135         Node child = template.getFirstChild();
136         while (child != null) {
137             if (child.getNodeType() != Node.ELEMENT_NODE) {
138                 child = child.getNextSibling();
139                 continue;
140             }
141             if ("TokenType".equals(child.getLocalName())) {
142                 String content = child.getTextContent();
143                 final SAMLVersion samlVersion = samlTokenSecurityEvent.getSamlAssertionWrapper().getSamlVersion();
144                 if (WSSConstants.NS_SAML11_TOKEN_PROFILE_TYPE.equals(content)
145                         && samlVersion != SAMLVersion.VERSION_11) {
146                     return "Policy enforces SAML V1.1 token but got " + samlVersion.toString();
147                 } else if (WSSConstants.NS_SAML20_TOKEN_PROFILE_TYPE.equals(content)
148                         && samlVersion != SAMLVersion.VERSION_20) {
149                     return "Policy enforces SAML V2.0 token but got " + samlVersion.toString();
150                 }
151             } else if ("KeyType".equals(child.getLocalName())) {
152                 String content = child.getTextContent();
153                 if (content.endsWith("SymmetricKey")) {
154                     Map<String, Key> subjectKeys = samlTokenSecurityEvent.getSecurityToken().getSecretKey();
155                     if (subjectKeys.isEmpty()) {
156                         return "Policy enforces SAML token with a symmetric key";
157                     }
158                 } else if (content.endsWith("PublicKey")) {
159                     PublicKey publicKey = samlTokenSecurityEvent.getSecurityToken().getPublicKey();
160                     X509Certificate[] x509Certificate = samlTokenSecurityEvent.getSecurityToken().getX509Certificates();
161                     if (publicKey == null && x509Certificate == null) {
162                         return "Policy enforces SAML token with an asymmetric key";
163                     }
164                 }
165             } else if ("Claims".equals(child.getLocalName())) {
166                 String errorMsg = validateClaims((Element) child, samlTokenSecurityEvent);
167                 if (errorMsg != null) {
168                     return errorMsg;
169                 }
170             }
171             child = child.getNextSibling();
172         }
173         return null;
174     }
175 
176     /**
177      * Check the issued token template against the received BinarySecurityToken
178      */
179     private String checkIssuedTokenTemplate(Element template, KerberosTokenSecurityEvent kerberosTokenSecurityEvent) {
180         Node child = template.getFirstChild();
181         while (child != null) {
182             if (child.getNodeType() != Node.ELEMENT_NODE) {
183                 child = child.getNextSibling();
184                 continue;
185             }
186             if ("TokenType".equals(child.getLocalName())) {
187                 String content = child.getTextContent();
188                 String valueType = kerberosTokenSecurityEvent.getKerberosTokenValueType();
189                 if (!content.equals(valueType)) {
190                     return "Policy enforces Kerberos token of type " + content + " but got " + valueType;
191                 }
192             }
193             child = child.getNextSibling();
194         }
195         return null;
196     }
197 
198     //todo I think the best is if we allow to set custom AssertionStates object on the policy-engine for
199     //custom validation -> task for WSS4j V2.1 ?
200     protected String validateClaims(Element claimsPolicy, SamlTokenSecurityEvent samlTokenSecurityEvent) throws WSSecurityException {
201         String dialect = claimsPolicy.getAttributeNS(null, "Dialect");
202         if (!DEFAULT_CLAIMS_NAMESPACE.equals(dialect)) {
203             return null;
204         }
205 
206         Node child = claimsPolicy.getFirstChild();
207         while (child != null) {
208             if (child.getNodeType() != Node.ELEMENT_NODE) {
209                 child = child.getNextSibling();
210                 continue;
211             }
212 
213             if ("ClaimType".equals(child.getLocalName())) {
214                 Element claimType = (Element) child;
215                 String claimTypeUri = claimType.getAttributeNS(null, "Uri");
216                 String claimTypeOptional = claimType.getAttributeNS(null, "Optional");
217 
218                 if (claimTypeOptional.length() == 0 || !Boolean.parseBoolean(claimTypeOptional)) {
219                     String errorMsg = findClaimInAssertion(samlTokenSecurityEvent.getSamlAssertionWrapper(), URI.create(claimTypeUri));
220                     if (errorMsg != null) {
221                         return errorMsg;
222                     }
223                 }
224             }
225             child = child.getNextSibling();
226         }
227         return null;
228     }
229 
230     protected String findClaimInAssertion(SamlAssertionWrapper samlAssertionWrapper, URI claimURI) {
231         if (samlAssertionWrapper.getSaml1() != null) {
232             return findClaimInAssertion(samlAssertionWrapper.getSaml1(), claimURI);
233         } else if (samlAssertionWrapper.getSaml2() != null) {
234             return findClaimInAssertion(samlAssertionWrapper.getSaml2(), claimURI);
235         }
236         return "Unsupported SAML version";
237     }
238 
239     protected String findClaimInAssertion(org.opensaml.saml.saml2.core.Assertion assertion, URI claimURI) {
240         List<org.opensaml.saml.saml2.core.AttributeStatement> attributeStatements =
241                 assertion.getAttributeStatements();
242         if (attributeStatements == null || attributeStatements.isEmpty()) {
243             return "Attribute " + claimURI + " not found in the SAMLAssertion";
244         }
245 
246         for (org.opensaml.saml.saml2.core.AttributeStatement statement : attributeStatements) {
247             List<org.opensaml.saml.saml2.core.Attribute> attributes = statement.getAttributes();
248             for (org.opensaml.saml.saml2.core.Attribute attribute : attributes) {
249 
250                 if (attribute.getName().equals(claimURI.toString())
251                         && attribute.getAttributeValues() != null && !attribute.getAttributeValues().isEmpty()) {
252                     return null;
253                 }
254             }
255         }
256         return "Attribute " + claimURI + " not found in the SAMLAssertion";
257     }
258 
259     protected String findClaimInAssertion(org.opensaml.saml.saml1.core.Assertion assertion, URI claimURI) {
260         List<org.opensaml.saml.saml1.core.AttributeStatement> attributeStatements =
261                 assertion.getAttributeStatements();
262         if (attributeStatements == null || attributeStatements.isEmpty()) {
263             return "Attribute " + claimURI + " not found in the SAMLAssertion";
264         }
265 
266         for (org.opensaml.saml.saml1.core.AttributeStatement statement : attributeStatements) {
267 
268             List<org.opensaml.saml.saml1.core.Attribute> attributes = statement.getAttributes();
269             for (org.opensaml.saml.saml1.core.Attribute attribute : attributes) {
270 
271                 URI attributeNamespace = URI.create(attribute.getAttributeNamespace());
272                 String desiredRole = attributeNamespace.relativize(claimURI).toString();
273                 if (attribute.getAttributeName().equals(desiredRole)
274                         && attribute.getAttributeValues() != null && !attribute.getAttributeValues().isEmpty()) {
275                     return null;
276                 }
277             }
278         }
279         return "Attribute " + claimURI + " not found in the SAMLAssertion";
280     }
281 }