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;
21  
22  /**
23   * WSDocInfo holds information about the document to process. It provides a
24   * method to store and access document information about BinarySecurityToken,
25   * used Crypto, and others.
26   *
27   * Using the Document's hash a caller can identify a document and get
28   * the stored information that me be necessary to process the document.
29   * The main usage for this is (are) the transformation functions that
30   * are called during Signature/Verification process.
31   */
32  
33  import java.util.ArrayList;
34  import java.util.Collections;
35  import java.util.HashMap;
36  import java.util.LinkedList;
37  import java.util.List;
38  import java.util.Map;
39  
40  import javax.xml.crypto.dom.DOMCryptoContext;
41  
42  import org.apache.wss4j.common.crypto.Crypto;
43  import org.apache.wss4j.common.ext.WSSecurityException;
44  import org.apache.wss4j.common.util.XMLUtils;
45  import org.apache.wss4j.dom.callback.CallbackLookup;
46  import org.apache.wss4j.dom.engine.WSSecurityEngineResult;
47  import org.w3c.dom.Document;
48  import org.w3c.dom.Element;
49  
50  public class WSDocInfo {
51      private Document doc;
52      private Crypto crypto;
53  
54      // Here we map the token "Id" to the token itself. The token "Id" is the key as it must be unique to guard
55      // against various wrapping attacks. The "Id" name/namespace is stored as part of the entry (along with the
56      // element), so that we know what namespace to use when setting the token on the crypto context for signature
57      // creation or validation
58      private final Map<String, TokenValue> tokens = new HashMap<>();
59  
60      private final List<WSSecurityEngineResult> results = new LinkedList<>();
61      private final Map<Integer, List<WSSecurityEngineResult>> actionResults = new HashMap<>();
62      private CallbackLookup callbackLookup;
63      private Element securityHeader;
64  
65      public WSDocInfo(Document doc) {
66          //
67          // This is a bit of a hack. When the Document is a SAAJ SOAPPart instance, it may
68          // be that the "owner" document of any child elements is an internal Document, rather
69          // than the SOAPPart. This is the case for the SUN SAAJ implementation.
70          //
71          if (doc != null && doc.getDocumentElement() != null) {
72              this.doc = doc.getDocumentElement().getOwnerDocument();
73          } else {
74              this.doc = doc;
75          }
76      }
77  
78      /**
79       * Clears the data stored in this object
80       */
81      public void clear() {
82          crypto = null;
83          doc = null;
84          callbackLookup = null;
85          securityHeader = null;
86          tokens.clear();
87          results.clear();
88          actionResults.clear();
89      }
90  
91      /**
92       * Store a token element for later retrieval. Before storing the token, we check for a
93       * previously processed token with the same (wsu/SAML) Id.
94       * @param element is the token element to store
95       */
96      public void addTokenElement(Element element) throws WSSecurityException {
97          addTokenElement(element, true);
98      }
99  
100     /**
101      * Store a token element for later retrieval. Before storing the token, we check for a
102      * previously processed token with the same (wsu/SAML) Id.
103      * @param element is the token element to store
104      * @param checkMultipleElements check for a previously stored element with the same Id.
105      */
106     public void addTokenElement(Element element, boolean checkMultipleElements) throws WSSecurityException {
107         if (element == null) {
108             return;
109         }
110 
111         if (element.hasAttributeNS(WSConstants.WSU_NS, "Id")) {
112             String id = element.getAttributeNS(WSConstants.WSU_NS, "Id");
113             TokenValue tokenValue = new TokenValue("Id", WSConstants.WSU_NS, element);
114             TokenValue previousValue = tokens.put(id, tokenValue);
115             if (checkMultipleElements && previousValue != null) {
116                 throw new WSSecurityException(
117                     WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "duplicateError"
118                 );
119             }
120         }
121 
122         if (element.hasAttributeNS(null, "Id")) {
123             String id = element.getAttributeNS(null, "Id");
124             TokenValue tokenValue = new TokenValue("Id", null, element);
125             TokenValue previousValue = tokens.put(id, tokenValue);
126             if (checkMultipleElements && previousValue != null) {
127                 throw new WSSecurityException(
128                     WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "duplicateError"
129                 );
130             }
131         }
132 
133         // SAML Assertions
134         if ("Assertion".equals(element.getLocalName())) {
135             if (WSConstants.SAML_NS.equals(element.getNamespaceURI())
136                 && element.hasAttributeNS(null, "AssertionID")) {
137                 String id = element.getAttributeNS(null, "AssertionID");
138                 TokenValue tokenValue = new TokenValue("AssertionID", null, element);
139                 TokenValue previousValue = tokens.put(id, tokenValue);
140                 if (checkMultipleElements && previousValue != null) {
141                     throw new WSSecurityException(
142                         WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "duplicateError"
143                     );
144                 }
145             } else if (WSConstants.SAML2_NS.equals(element.getNamespaceURI())
146                 && element.hasAttributeNS(null, "ID")) {
147                 String id = element.getAttributeNS(null, "ID");
148                 TokenValue tokenValue = new TokenValue("ID", null, element);
149                 TokenValue previousValue = tokens.put(id, tokenValue);
150                 if (checkMultipleElements && previousValue != null) {
151                     throw new WSSecurityException(
152                         WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN, "duplicateError"
153                     );
154                 }
155             }
156         }
157 
158     }
159 
160 
161     /**
162      * Get a token Element for the given Id. The Id can be either a wsu:Id or a
163      * SAML AssertionID/ID.
164      * @param uri is the (relative) uri of the id
165      * @return the token element or null if nothing found
166      */
167     public Element getTokenElement(String uri) {
168         String id = XMLUtils.getIDFromReference(uri);
169         if (id == null) {
170             return null;
171         }
172 
173         TokenValue token = tokens.get(id);
174         if (token != null) {
175             return token.getToken();
176         }
177 
178         return null;
179     }
180 
181     /**
182      * Set all stored tokens on the DOMCryptoContext argument
183      * @param context
184      */
185     public void setTokensOnContext(DOMCryptoContext context) {
186         if (!tokens.isEmpty() && context != null) {
187             for (Map.Entry<String, TokenValue> entry : tokens.entrySet()) {
188                 TokenValue tokenValue = entry.getValue();
189                 context.setIdAttributeNS(tokenValue.getToken(), tokenValue.getIdNamespace(),
190                                          tokenValue.getIdName());
191             }
192         }
193     }
194 
195     public void setTokenOnContext(String uri, DOMCryptoContext context) {
196         String id = XMLUtils.getIDFromReference(uri);
197         if (id == null || context == null) {
198             return;
199         }
200 
201         TokenValue tokenValue = tokens.get(id);
202         if (tokenValue != null) {
203             context.setIdAttributeNS(tokenValue.getToken(), tokenValue.getIdNamespace(),
204                                      tokenValue.getIdName());
205         }
206     }
207 
208 
209     /**
210      * Store a WSSecurityEngineResult for later retrieval.
211      * @param result is the WSSecurityEngineResult to store
212      */
213     public void addResult(WSSecurityEngineResult result) {
214         results.add(result);
215         Integer resultTag = (Integer)result.get(WSSecurityEngineResult.TAG_ACTION);
216         if (resultTag != null) {
217             List<WSSecurityEngineResult> storedResults = actionResults.get(resultTag);
218             if (storedResults == null) {
219                 storedResults = new ArrayList<>();
220             }
221             storedResults.add(result);
222             actionResults.put(resultTag, storedResults);
223         }
224     }
225 
226     /**
227      * Get a copy of the security results list. Modifying the subsequent list does not
228      * change the internal results list.
229      */
230     public List<WSSecurityEngineResult> getResults() {
231         if (results.isEmpty()) {
232             return Collections.emptyList();
233         }
234         return new ArrayList<>(results);
235     }
236 
237     /**
238      * Return a copy of the map between security actions + results. Modifying the subsequent
239      * map does not change the internal map.
240      */
241     public Map<Integer, List<WSSecurityEngineResult>> getActionResults() {
242         if (actionResults.isEmpty()) {
243             return Collections.emptyMap();
244         }
245         return new HashMap<>(actionResults);
246     }
247 
248     /**
249      * Get a WSSecurityEngineResult for the given Id.
250      * @param uri is the (relative) uri of the id
251      * @return the WSSecurityEngineResult or null if nothing found
252      */
253     public WSSecurityEngineResult getResult(String uri) {
254         String id = XMLUtils.getIDFromReference(uri);
255         if (id != null && !results.isEmpty()) {
256             for (WSSecurityEngineResult result : results) {
257                 String cId = (String)result.get(WSSecurityEngineResult.TAG_ID);
258                 if (id.equals(cId)) {
259                     return result;
260                 }
261             }
262         }
263         return null;    //NOPMD
264     }
265 
266     /**
267      * Get a unmodifiable list of WSSecurityEngineResults of the given Integer tag
268      */
269     public List<WSSecurityEngineResult> getResultsByTag(Integer tag) {
270         if (actionResults.isEmpty() || !actionResults.containsKey(tag)) {
271             return Collections.emptyList();
272         }
273 
274         return Collections.unmodifiableList(actionResults.get(tag));
275     }
276 
277     /**
278      * See whether we have a WSSecurityEngineResult of the given Integer tag for the given Id
279      */
280     public boolean hasResult(Integer tag, String uri) {
281         String id = XMLUtils.getIDFromReference(uri);
282         if (id == null || uri == null || uri.length() == 0) {
283             return false;
284         }
285 
286         if (!actionResults.isEmpty() && actionResults.containsKey(tag)) {
287             for (WSSecurityEngineResult result : actionResults.get(tag)) {
288                 String cId = (String)result.get(WSSecurityEngineResult.TAG_ID);
289                 if (id.equals(cId)) {
290                     return true;
291                 }
292             }
293         }
294 
295         return false;
296     }
297 
298     /**
299      * @return the signature crypto class used to process
300      *         the signature/verify
301      */
302     public Crypto getCrypto() {
303         return crypto;
304     }
305 
306     /**
307      * @return the document
308      */
309     public Document getDocument() {
310         return doc;
311     }
312 
313     /**
314      * @param crypto is the signature crypto class used to
315      *               process signature/verify
316      */
317     public void setCrypto(Crypto crypto) {
318         this.crypto = crypto;
319     }
320 
321     /**
322      * @param callbackLookup The CallbackLookup object to retrieve elements
323      */
324     public void setCallbackLookup(CallbackLookup callbackLookup) {
325         this.callbackLookup = callbackLookup;
326     }
327 
328     /**
329      * @return the CallbackLookup object to retrieve elements
330      */
331     public CallbackLookup getCallbackLookup() {
332         return callbackLookup;
333     }
334 
335     /**
336      * @return the wsse header being processed
337      */
338     public Element getSecurityHeader() {
339         return securityHeader;
340     }
341 
342     /**
343      * Sets the wsse header being processed
344      *
345      * @param securityHeader
346      */
347     public void setSecurityHeader(Element securityHeader) {
348         this.securityHeader = securityHeader;
349     }
350 
351     private static class TokenValue {
352         private final String idName;
353         private final String idNamespace;
354         private final Element token;
355 
356         TokenValue(String idName, String idNamespace, Element token) {
357             this.idName = idName;
358             this.idNamespace = idNamespace;
359             this.token = token;
360         }
361 
362         public String getIdName() {
363             return idName;
364         }
365 
366         public String getIdNamespace() {
367             return idNamespace;
368         }
369         public Element getToken() {
370             return token;
371         }
372     }
373 
374 }