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.message.token;
21  
22  import java.security.Principal;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.Map;
26  import java.util.Map.Entry;
27  
28  import javax.xml.namespace.QName;
29  
30  import org.apache.wss4j.dom.WSConstants;
31  import org.apache.wss4j.common.ext.WSSecurityException;
32  import org.apache.wss4j.common.bsp.BSPEnforcer;
33  import org.apache.wss4j.common.derivedKey.ConversationConstants;
34  import org.apache.wss4j.common.derivedKey.DerivedKeyUtils;
35  import org.apache.wss4j.common.principal.WSDerivedKeyTokenPrincipal;
36  import org.apache.wss4j.common.token.SecurityTokenReference;
37  import org.apache.wss4j.common.util.DOM2Writer;
38  import org.apache.wss4j.common.util.XMLUtils;
39  import org.apache.wss4j.dom.util.WSSecurityUtil;
40  import org.w3c.dom.Document;
41  import org.w3c.dom.Element;
42  import org.w3c.dom.Node;
43  import org.w3c.dom.Text;
44  
45  /**
46   <DerivedKeyToken wsu:Id="..." wsc:Algorithm="...">
47   <SecurityTokenReference>...</SecurityTokenReference>
48   <Properties>...</Properties>
49   <Generation>...</Generation>
50   <Offset>...</Offset>
51   <Length>...</Length>
52   <Label>...</Label>
53   <Nonce>...</Nonce>
54   </DerivedKeyToken>
55   */
56  
57  public class DerivedKeyToken {
58  
59      private static final org.slf4j.Logger LOG =
60          org.slf4j.LoggerFactory.getLogger(DerivedKeyToken.class);
61  
62      // These are the elements that are used to create the SecurityContextToken
63      private Element element;
64      private Element elementSecurityTokenReference;
65      private Element elementProperties;
66      private Element elementGeneration;
67      private Element elementOffset;
68      private Element elementLength;
69      private Element elementLabel;
70      private Element elementNonce;
71      private int length = 32;
72      private int offset = 0;
73      private int generation = -1;
74  
75      private String ns;
76  
77      private final BSPEnforcer bspEnforcer;
78  
79      /**
80       * This will create an empty DerivedKeyToken
81       *
82       * @param doc The DOM document
83       */
84      public DerivedKeyToken(Document doc) throws WSSecurityException {
85          this(ConversationConstants.DEFAULT_VERSION, doc);
86      }
87  
88      /**
89       * This will create an empty DerivedKeyToken
90       *
91       * @param doc The DOM document
92       */
93      public DerivedKeyToken(int version, Document doc) throws WSSecurityException {
94          LOG.debug("DerivedKeyToken: created");
95  
96          ns = ConversationConstants.getWSCNs(version);
97          element =
98              doc.createElementNS(ns, ConversationConstants.WSC_PREFIX + ":"
99                  + ConversationConstants.DERIVED_KEY_TOKEN_LN);
100         XMLUtils.setNamespace(element, ns, ConversationConstants.WSC_PREFIX);
101         bspEnforcer = new BSPEnforcer();
102     }
103 
104     /**
105      * This will create a DerivedKeyToken object with the given DerivedKeyToken element
106      *
107      * @param elem The DerivedKeyToken DOM element
108      * @param bspEnforcer a BSPEnforcer instance to enforce BSP rules
109      * @throws WSSecurityException If the element is not a derived key token
110      */
111     public DerivedKeyToken(Element elem, BSPEnforcer bspEnforcer) throws WSSecurityException {
112         LOG.debug("DerivedKeyToken: created : element constructor");
113         element = elem;
114         this.bspEnforcer = bspEnforcer;
115         QName el = new QName(element.getNamespaceURI(), element.getLocalName());
116 
117         if (!(el.equals(ConversationConstants.DERIVED_KEY_TOKEN_QNAME_05_02)
118             || el.equals(ConversationConstants.DERIVED_KEY_TOKEN_QNAME_05_12))) {
119             throw new WSSecurityException(
120                 WSSecurityException.ErrorCode.INVALID_SECURITY_TOKEN
121             );
122         }
123         elementSecurityTokenReference =
124             XMLUtils.getDirectChildElement(
125                 element,
126                 ConversationConstants.SECURITY_TOKEN_REFERENCE_LN,
127                 WSConstants.WSSE_NS
128             );
129 
130         ns = el.getNamespaceURI();
131 
132         elementProperties =
133             XMLUtils.getDirectChildElement(
134                 element, ConversationConstants.PROPERTIES_LN, ns
135             );
136         elementGeneration =
137             XMLUtils.getDirectChildElement(
138                 element, ConversationConstants.GENERATION_LN, ns
139             );
140         elementOffset =
141             XMLUtils.getDirectChildElement(
142                 element, ConversationConstants.OFFSET_LN, ns
143             );
144         elementLength =
145             XMLUtils.getDirectChildElement(
146                 element, ConversationConstants.LENGTH_LN, ns
147             );
148         elementLabel =
149             XMLUtils.getDirectChildElement(
150                 element, ConversationConstants.LABEL_LN, ns
151             );
152         elementNonce =
153             XMLUtils.getDirectChildElement(
154                 element, ConversationConstants.NONCE_LN, ns
155             );
156 
157         if (elementLength != null) {
158             Text text = getFirstNode(elementLength);
159             if (text != null) {
160                 try {
161                     length = Integer.parseInt(text.getData());
162                 } catch (NumberFormatException ex) {
163                     throw new WSSecurityException(
164                             WSSecurityException.ErrorCode.FAILURE, ex, "decoding.general"
165                     );
166                 }
167             }
168         }
169 
170         if (elementOffset != null) {
171             Text text = getFirstNode(elementOffset);
172             if (text != null) {
173                 try {
174                     offset = Integer.parseInt(text.getData());
175                 } catch (NumberFormatException ex) {
176                     throw new WSSecurityException(
177                             WSSecurityException.ErrorCode.FAILURE, ex, "decoding.general"
178                     );
179                 }
180             }
181         }
182 
183         if (elementGeneration != null) {
184             Text text = getFirstNode(elementGeneration);
185             if (text != null) {
186                 try {
187                     generation = Integer.parseInt(text.getData());
188                 } catch (NumberFormatException ex) {
189                     throw new WSSecurityException(
190                             WSSecurityException.ErrorCode.FAILURE, ex, "decoding.general"
191                     );
192                 }
193             }
194         }
195     }
196 
197     /**
198      * Add the WSU Namespace to this DKT. The namespace is not added by default for
199      * efficiency purposes.
200      */
201     public void addWSUNamespace() {
202         element.setAttributeNS(XMLUtils.XMLNS_NS, "xmlns:" + WSConstants.WSU_PREFIX, WSConstants.WSU_NS);
203     }
204 
205     /**
206      * Sets the security token reference of the derived key token
207      * This is the reference to the shared secret used in the conversation/context
208      *
209      * @param ref Security token reference
210      */
211     public void setSecurityTokenReference(SecurityTokenReference ref) {
212         elementSecurityTokenReference = ref.getElement();
213         WSSecurityUtil.prependChildElement(element, ref.getElement());
214     }
215 
216     public void setSecurityTokenReference(Element elem) {
217         elementSecurityTokenReference = elem;
218         WSSecurityUtil.prependChildElement(element, elem);
219     }
220 
221     /**
222      * Returns the SecurityTokenReference of the derived key token
223      *
224      * @return the Security Token Reference of the derived key token
225      * @throws WSSecurityException
226      */
227     public SecurityTokenReference getSecurityTokenReference() throws WSSecurityException {
228         if (elementSecurityTokenReference != null) {
229             return new SecurityTokenReference(elementSecurityTokenReference, bspEnforcer);
230         }
231         return null;
232     }
233 
234     /**
235      * Returns the SecurityTokenReference element of the derived key token
236      *
237      * @return the Security Token Reference element of the derived key token
238      */
239     public Element getSecurityTokenReferenceElement() {
240         return elementSecurityTokenReference;
241     }
242 
243     /**
244      * This adds a property into
245      * /DerivedKeyToken/Properties
246      *
247      * @param propName  Name of the property
248      * @param propValue Value of the property
249      */
250     private void addProperty(String propName, String propValue) {
251         if (elementProperties == null) { //Create the properties element if it is not there
252             elementProperties =
253                 element.getOwnerDocument().createElementNS(
254                     ns, ConversationConstants.WSC_PREFIX + ":" + ConversationConstants.PROPERTIES_LN
255                 );
256             element.appendChild(elementProperties);
257         }
258         Element tempElement =
259             element.getOwnerDocument().createElementNS(ns, ConversationConstants.WSC_PREFIX + ":"
260                 + propName);
261         tempElement.appendChild(element.getOwnerDocument().createTextNode(propValue));
262 
263         elementProperties.appendChild(tempElement);
264     }
265 
266     /**
267      * This is used to set the Name, Label and Nonce element values in the properties element
268      * <b>At this point I'm not sure if these are the only properties that will appear in the
269      * <code>Properties</code> element. There fore this method is provided
270      * If this is not required feel free to remove this :D
271      * </b>
272      *
273      * @param name  Value of the Properties/Name element
274      * @param label Value of the Properties/Label element
275      * @param nonce Value of the Properties/Nonce element
276      */
277     public void setProperties(String name, String label, String nonce) {
278         Map<String, String> table = new HashMap<>();
279         table.put("Name", name);
280         table.put("Label", label);
281         table.put("Nonce", nonce);
282         setProperties(table);
283     }
284 
285     /**
286      * If there are other types of properties other than Name, Label and Nonce
287      * This is provided for extensibility purposes
288      *
289      * @param properties The properties and values in a Map
290      */
291     public void setProperties(Map<String, String> properties) {
292         if (properties != null && !properties.isEmpty()) {
293             for (Entry<String, String> entry : properties.entrySet()) {
294                 String propertyName = entry.getValue();
295                 //Check whether this property is already there
296                 //If so change the value
297                 Element node =
298                     XMLUtils.findElement(elementProperties, propertyName, ns);
299                 if (node != null) { //If the node is not null
300                     Text node1 = getFirstNode(node);
301                     if (node1 != null) {
302                         node1.setData(properties.get(propertyName));
303                     }
304                 } else {
305                     addProperty(propertyName, properties.get(propertyName));
306                 }
307             }
308         }
309     }
310 
311     public Map<String, String> getProperties() {
312         if (elementProperties != null) {
313             Map<String, String> table = new HashMap<>();
314             Node node = elementProperties.getFirstChild();
315             while (node != null) {
316                 if (Node.ELEMENT_NODE == node.getNodeType()) {
317                     Text text = getFirstNode((Element) node);
318                     if (text != null) {
319                         table.put(node.getNodeName(), text.getData());
320                     }
321                 }
322                 node = node.getNextSibling();
323             }
324             return table;
325         }
326         return Collections.emptyMap();
327     }
328 
329     /**
330      * Sets the length of the derived key
331      *
332      * @param length The length of the derived key as a long
333      */
334     public void setLength(int length) {
335         elementLength =
336             element.getOwnerDocument().createElementNS(
337                 ns, ConversationConstants.WSC_PREFIX + ":" + ConversationConstants.LENGTH_LN
338             );
339         elementLength.appendChild(
340             element.getOwnerDocument().createTextNode(Long.toString(length))
341         );
342         element.appendChild(elementLength);
343         this.length = length;
344     }
345 
346     public int getLength() {
347         return length;
348     }
349 
350     /**
351      * Sets the offset
352      *
353      * @param offset The offset value as an integer
354      */
355     public void setOffset(int offset) throws WSSecurityException {
356         //This element MUST NOT be used if the <Generation> element is specified
357         if (elementGeneration == null) {
358             elementOffset =
359                 element.getOwnerDocument().createElementNS(
360                     ns, ConversationConstants.WSC_PREFIX + ":" + ConversationConstants.OFFSET_LN
361                 );
362             elementOffset.appendChild(
363                 element.getOwnerDocument().createTextNode(Integer.toString(offset))
364             );
365             element.appendChild(elementOffset);
366         } else {
367             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "offsetError");
368         }
369         this.offset = offset;
370     }
371 
372     public int getOffset() {
373         return offset;
374     }
375 
376     /**
377      * Sets the generation of the derived key
378      *
379      * @param generation generation value as an integer
380      */
381     public void setGeneration(int generation) throws WSSecurityException {
382         //This element MUST NOT be used if the <Offset> element is specified
383         if (elementOffset == null) {
384             elementGeneration =
385                 element.getOwnerDocument().createElementNS(
386                     ns, ConversationConstants.WSC_PREFIX + ":" + ConversationConstants.GENERATION_LN
387                 );
388             elementGeneration.appendChild(
389                 element.getOwnerDocument().createTextNode(Integer.toString(generation))
390             );
391             element.appendChild(elementGeneration);
392         } else {
393             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "offsetError");
394         }
395         this.generation = generation;
396     }
397 
398     public int getGeneration() {
399         return generation;
400     }
401 
402     /**
403      * Sets the label of the derived key
404      *
405      * @param label Label value as a string
406      */
407     public void setLabel(String label) {
408         elementLabel =
409             element.getOwnerDocument().createElementNS(
410                 ns, ConversationConstants.WSC_PREFIX + ":" + ConversationConstants.LABEL_LN
411             );
412         elementLabel.appendChild(element.getOwnerDocument().createTextNode(label));
413         element.appendChild(elementLabel);
414     }
415 
416     /**
417      * Sets the nonce value of the derived key
418      *
419      * @param nonce Nonce value as a string
420      */
421     public void setNonce(String nonce) {
422         elementNonce =
423             element.getOwnerDocument().createElementNS(
424                 ns, ConversationConstants.WSC_PREFIX + ":" + ConversationConstants.NONCE_LN
425             );
426         elementNonce.appendChild(element.getOwnerDocument().createTextNode(nonce));
427         element.appendChild(elementNonce);
428     }
429 
430     /**
431      * Returns the label of the derived key token
432      *
433      * @return Label of the derived key token
434      */
435     public String getLabel() {
436         if (elementLabel != null) {
437             Text text = getFirstNode(elementLabel);
438             if (text != null) {
439                 return text.getData();
440             }
441         }
442         return null;
443     }
444 
445     /**
446      * Return the nonce of the derived key token
447      *
448      * @return Nonce of the derived key token
449      */
450     public String getNonce() {
451         if (elementNonce != null) {
452             Text text = getFirstNode(elementNonce);
453             if (text != null) {
454                 return text.getData();
455             }
456         }
457         return null;
458     }
459 
460     /**
461      * Returns the first text node of an element.
462      *
463      * @param e the element to get the node from
464      * @return the first text node or <code>null</code> if node
465      *         is null or is not a text node
466      */
467     private Text getFirstNode(Element e) {
468         Node node = e.getFirstChild();
469         return node != null && Node.TEXT_NODE == node.getNodeType() ? (Text) node : null;
470     }
471 
472     /**
473      * Returns the dom element of this <code>SecurityContextToken</code> object.
474      *
475      * @return the DerivedKeyToken element
476      */
477     public Element getElement() {
478         return element;
479     }
480 
481     /**
482      * Returns the string representation of the token.
483      *
484      * @return a XML string representation
485      */
486     public String toString() {
487         return DOM2Writer.nodeToString(element);
488     }
489 
490     /**
491      * Gets the id.
492      *
493      * @return the value of the <code>wsu:Id</code> attribute of this
494      *         DerivedKeyToken
495      */
496     public String getID() {
497         return element.getAttributeNS(WSConstants.WSU_NS, "Id");
498     }
499 
500     /**
501      * Set the id of this derived key token.
502      *
503      * @param id the value for the <code>wsu:Id</code> attribute of this
504      *           DerivedKeyToken
505      */
506     public void setID(String id) {
507         element.setAttributeNS(WSConstants.WSU_NS, WSConstants.WSU_PREFIX + ":Id", id);
508     }
509 
510     /**
511      * Gets the derivation algorithm
512      *
513      * @return the value of the <code>wsc:Algorithm</code> attribute of this
514      *         DerivedKeyToken
515      */
516     public String getAlgorithm() {
517         String algo = element.getAttributeNS(ns, "Algorithm");
518         if (algo.length() == 0) {
519             return ConversationConstants.DerivationAlgorithm.P_SHA_1;
520         } else {
521             return algo;
522         }
523     }
524 
525     /**
526      * Create a WSDerivedKeyTokenPrincipal from this DerivedKeyToken object
527      */
528     public Principal createPrincipal() throws WSSecurityException {
529         WSDerivedKeyTokenPrincipal principal = new WSDerivedKeyTokenPrincipal(getID());
530         principal.setNonce(getNonce());
531         principal.setLabel(getLabel());
532         principal.setLength(getLength());
533         principal.setOffset(getOffset());
534         principal.setAlgorithm(getAlgorithm());
535 
536         String basetokenId = null;
537         SecurityTokenReference securityTokenReference = getSecurityTokenReference();
538         if (securityTokenReference != null && securityTokenReference.getReference() != null) {
539             basetokenId = securityTokenReference.getReference().getURI();
540             basetokenId = XMLUtils.getIDFromReference(basetokenId);
541         } else if (securityTokenReference != null) {
542             // KeyIdentifier
543             basetokenId = securityTokenReference.getKeyIdentifierValue();
544         }
545         principal.setBasetokenId(basetokenId);
546 
547         return principal;
548     }
549 
550     /**
551      * Set the derivation algorithm of this derived key token.
552      *
553      * @param algo the value for the <code>Algorithm</code> attribute of this
554      *             DerivedKeyToken
555      */
556     public void setAlgorithm(String algo) {
557         if (algo != null) {
558             element.setAttributeNS(ns, "Algorithm", algo);
559         }
560     }
561 
562     /**
563      * Derive a key from this DerivedKeyToken instance
564      * @param length
565      * @param secret
566      * @throws WSSecurityException
567      */
568     public byte[] deriveKey(int length, byte[] secret) throws WSSecurityException {
569         try {
570             byte[] nonce = org.apache.xml.security.utils.XMLUtils.decode(getNonce());
571             return DerivedKeyUtils.deriveKey(getAlgorithm(), getLabel(), length, secret, nonce, getOffset());
572         } catch (Exception e) {
573             throw new WSSecurityException(
574                 WSSecurityException.ErrorCode.FAILURE, e
575             );
576         }
577     }
578 
579     @Override
580     public int hashCode() {
581         int result = 17;
582         String algorithm = getAlgorithm();
583         if (algorithm != null) {
584             result = 31 * result + algorithm.hashCode();
585         }
586         try {
587             SecurityTokenReference tokenReference = getSecurityTokenReference();
588             if (tokenReference != null) {
589                 result = 31 * result + tokenReference.hashCode();
590             }
591         } catch (WSSecurityException e) {
592             LOG.error(e.getMessage(), e);
593         }
594 
595         Map<String, String> properties = getProperties();
596         if (!properties.isEmpty()) {
597             result = 31 * result + properties.hashCode();
598         }
599         int generation = getGeneration();
600         if (generation != -1) {
601             result = 31 * result + generation;
602         }
603         int offset = getOffset();
604         if (offset != -1) {
605             result = 31 * result + offset;
606         }
607         int length = getLength();
608         if (length != -1) {
609             result = 31 * result + length;
610         }
611         String label = getLabel();
612         if (label != null) {
613             result = 31 * result + label.hashCode();
614         }
615         String nonce = getNonce();
616         if (nonce != null) {
617             result = 31 * result + nonce.hashCode();
618         }
619 
620         return result;
621     }
622 
623     @Override
624     public boolean equals(Object object) {
625         if (!(object instanceof DerivedKeyToken)) {
626             return false;
627         }
628         DerivedKeyToken token = (DerivedKeyToken)object;
629         if (!compare(getAlgorithm(), token.getAlgorithm())) {
630             return false;
631         }
632         try {
633             if (getSecurityTokenReference() != null
634                 && !getSecurityTokenReference().equals(token.getSecurityTokenReference())
635                 || getSecurityTokenReference() == null && token.getSecurityTokenReference() != null) {
636                 return false;
637             }
638         } catch (WSSecurityException e) {
639             LOG.error(e.getMessage(), e);
640             return false;
641         }
642         if (!compare(getProperties(), token.getProperties())) {
643             return false;
644         }
645         if (getGeneration() != token.getGeneration()) {
646             return false;
647         }
648         if (getOffset() != token.getOffset()) {
649             return false;
650         }
651         if (getLength() != token.getLength()) {
652             return false;
653         }
654         if (!compare(getLabel(), token.getLabel())) {
655             return false;
656         }
657         return compare(getNonce(), token.getNonce());
658     }
659 
660     private boolean compare(String item1, String item2) {
661         if (item1 == null && item2 != null) {
662             return false;
663         } else if (item1 != null && !item1.equals(item2)) {
664             return false;
665         }
666         return true;
667     }
668 
669     private boolean compare(Map<String, String> item1, Map<String, String> item2) {
670         if (item1 == null && item2 != null) {
671             return false;
672         } else if (item1 != null && !item1.equals(item2)) {
673             return false;
674         }
675         return true;
676     }
677 }