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