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 org.apache.ws.security.WSConstants;
23  import org.apache.ws.security.WSPasswordCallback;
24  import org.apache.ws.security.WSSecurityException;
25  import org.apache.ws.security.WSUsernameTokenPrincipal;
26  import org.apache.ws.security.handler.RequestData;
27  import org.apache.ws.security.util.DOM2Writer;
28  import org.apache.ws.security.util.WSSecurityUtil;
29  import org.apache.ws.security.util.XmlSchemaDateFormat;
30  import org.apache.ws.security.util.Base64;
31  import org.w3c.dom.Document;
32  import org.w3c.dom.Element;
33  import org.w3c.dom.Node;
34  import org.w3c.dom.Text;
35  
36  import javax.crypto.Mac;
37  import javax.crypto.spec.SecretKeySpec;
38  import javax.security.auth.callback.Callback;
39  import javax.security.auth.callback.UnsupportedCallbackException;
40  import javax.xml.namespace.QName;
41  
42  import java.io.IOException;
43  import java.security.MessageDigest;
44  import java.security.NoSuchAlgorithmException;
45  import java.security.Principal;
46  import java.text.SimpleDateFormat;
47  import java.util.Arrays;
48  import java.util.Date;
49  import java.util.List;
50  import java.text.DateFormat;
51  import java.util.TimeZone;
52  
53  /**
54   * UsernameToken according to WS Security specifications, UsernameToken profile.
55   * 
56   * Enhanced to support digest password type for username token signature
57   * Enhanced to support passwordless usernametokens as allowed by spec.
58   * 
59   * @author Davanum Srinivas (dims@yahoo.com)
60   * @author Werner Dittmann (Werner.Dittmann@t-online.de)
61   */
62  public class UsernameToken {
63      public static final String BASE64_ENCODING = WSConstants.SOAPMESSAGE_NS + "#Base64Binary";
64      public static final String PASSWORD_TYPE = "passwordType";
65      public static final int DEFAULT_ITERATION = 1000;
66      public static final QName TOKEN = 
67          new QName(WSConstants.WSSE_NS, WSConstants.USERNAME_TOKEN_LN);
68      
69      private static final org.apache.commons.logging.Log LOG = 
70          org.apache.commons.logging.LogFactory.getLog(UsernameToken.class);
71      private static final boolean DO_DEBUG = LOG.isDebugEnabled();
72  
73      protected Element element = null;
74      protected Element elementUsername = null;
75      protected Element elementPassword = null;
76      protected Element elementNonce = null;
77      protected Element elementCreated = null;
78      protected Element elementSalt = null;
79      protected Element elementIteration = null;
80      protected String passwordType = null;
81      protected boolean hashed = true;
82      private String rawPassword;        // enhancement by Alberto Coletti
83      private boolean passwordsAreEncoded = false;
84      private boolean bspCompliantDerivedKey = true;
85      
86      /**
87       * Constructs a <code>UsernameToken</code> object and parses the
88       * <code>wsse:UsernameToken</code> element to initialize it.
89       * 
90       * @param elem the <code>wsse:UsernameToken</code> element that contains
91       *             the UsernameToken data
92       * @throws WSSecurityException
93       */
94      public UsernameToken(Element elem) throws WSSecurityException {
95          this (elem, false, true);
96      }
97  
98      /**
99       * Constructs a <code>UsernameToken</code> object and parses the
100      * <code>wsse:UsernameToken</code> element to initialize it.
101      * 
102      * @param elem the <code>wsse:UsernameToken</code> element that contains
103      *             the UsernameToken data
104      * @param allowNamespaceQualifiedPasswordTypes whether to allow (wsse)
105      *        namespace qualified password types or not (for interop with WCF)
106      * @param bspCompliant whether the UsernameToken processing complies with the BSP spec
107      * @throws WSSecurityException
108      */
109     public UsernameToken(
110         Element elem, 
111         boolean allowNamespaceQualifiedPasswordTypes,
112         boolean bspCompliant
113     ) throws WSSecurityException {
114         element = elem;
115         QName el = new QName(element.getNamespaceURI(), element.getLocalName());
116         if (!el.equals(TOKEN)) {
117             throw new WSSecurityException(
118                 WSSecurityException.INVALID_SECURITY_TOKEN,
119                 "badUsernameToken"
120             );
121         }
122         elementUsername = 
123             WSSecurityUtil.getDirectChildElement(
124                 element, WSConstants.USERNAME_LN, WSConstants.WSSE_NS
125             );
126         elementPassword = 
127             WSSecurityUtil.getDirectChildElement(
128                 element, WSConstants.PASSWORD_LN, WSConstants.WSSE_NS
129             );
130         elementNonce = 
131             WSSecurityUtil.getDirectChildElement(
132                 element, WSConstants.NONCE_LN, WSConstants.WSSE_NS
133             );
134         elementCreated = 
135             WSSecurityUtil.getDirectChildElement(
136                 element, WSConstants.CREATED_LN, WSConstants.WSU_NS
137             );
138         elementSalt = 
139             WSSecurityUtil.getDirectChildElement(
140                 element, WSConstants.SALT_LN, WSConstants.WSSE11_NS
141             );
142         elementIteration = 
143             WSSecurityUtil.getDirectChildElement(
144                 element, WSConstants.ITERATION_LN, WSConstants.WSSE11_NS
145             );
146         if (elementUsername == null) {
147             throw new WSSecurityException(
148                 WSSecurityException.INVALID_SECURITY_TOKEN,
149                 "badUsernameToken"
150             );
151         }
152         
153         if (bspCompliant) {
154             checkBSPCompliance();
155         }
156         
157         hashed = false;
158         if (elementSalt != null) {
159             //
160             // If the UsernameToken is to be used for key derivation, the (1.1)
161             // spec says that it cannot contain a password, and it must contain
162             // an Iteration element
163             //
164             if (elementPassword != null || elementIteration == null) {
165                 throw new WSSecurityException(
166                     WSSecurityException.INVALID_SECURITY_TOKEN,
167                     "badUsernameToken"
168                 );
169             }
170             return;
171         }
172         
173         // Guard against a malicious user sending a bogus iteration value
174         if (elementIteration != null) {
175             String iter = nodeString(elementIteration);
176             if (iter != null) {
177                 int iterInt = Integer.parseInt(iter);
178                 if (iterInt < 0 || iterInt > 10000) {
179                     throw new WSSecurityException(
180                         WSSecurityException.INVALID_SECURITY_TOKEN,
181                         "badUsernameToken"
182                     );
183                 }
184             }
185         }
186         
187         if (elementPassword != null) {
188             if (elementPassword.hasAttribute(WSConstants.PASSWORD_TYPE_ATTR)) {
189                 passwordType = elementPassword.getAttribute(WSConstants.PASSWORD_TYPE_ATTR);
190             } else if (elementPassword.hasAttributeNS(
191                 WSConstants.WSSE_NS, WSConstants.PASSWORD_TYPE_ATTR)
192             ) {
193                 if (allowNamespaceQualifiedPasswordTypes) {
194                     passwordType = 
195                         elementPassword.getAttributeNS(
196                             WSConstants.WSSE_NS, WSConstants.PASSWORD_TYPE_ATTR
197                         );
198                 } else {
199                     throw new WSSecurityException(
200                         WSSecurityException.INVALID_SECURITY_TOKEN,
201                         "badUsernameToken"
202                     );
203                 }
204             }
205             
206         }
207         if (WSConstants.PASSWORD_DIGEST.equals(passwordType)) {
208             hashed = true;
209             if (elementNonce == null || elementCreated == null) {
210                 throw new WSSecurityException(
211                     WSSecurityException.INVALID_SECURITY_TOKEN,
212                     "badUsernameToken"
213                 );
214             }
215         }
216     }
217 
218     /**
219      * Constructs a <code>UsernameToken</code> object according to the defined
220      * parameters. <p/> This constructs set the password encoding to
221      * {@link WSConstants#PASSWORD_DIGEST}
222      * 
223      * @param doc the SOAP envelope as <code>Document</code>
224      */
225     public UsernameToken(boolean milliseconds, Document doc) {
226         this(milliseconds, doc, WSConstants.PASSWORD_DIGEST);
227     }
228 
229     /**
230      * Constructs a <code>UsernameToken</code> object according to the defined
231      * parameters.
232      * 
233      * @param doc the SOAP envelope as <code>Document</code>
234      * @param pwType the required password encoding, either
235      *               {@link WSConstants#PASSWORD_DIGEST} or
236      *               {@link WSConstants#PASSWORD_TEXT} or 
237      *               {@link WSConstants#PW_NONE} <code>null</code> if no
238      *               password required
239      */
240     public UsernameToken(boolean milliseconds, Document doc, String pwType) {
241         element = 
242             doc.createElementNS(WSConstants.WSSE_NS, "wsse:" + WSConstants.USERNAME_TOKEN_LN);
243 
244         elementUsername = 
245             doc.createElementNS(WSConstants.WSSE_NS, "wsse:" + WSConstants.USERNAME_LN);
246         elementUsername.appendChild(doc.createTextNode(""));
247         element.appendChild(elementUsername);
248 
249         if (pwType != null) {
250             elementPassword = 
251                 doc.createElementNS(WSConstants.WSSE_NS, "wsse:" + WSConstants.PASSWORD_LN);
252             elementPassword.appendChild(doc.createTextNode(""));
253             element.appendChild(elementPassword);
254 
255             passwordType = pwType;
256             if (passwordType.equals(WSConstants.PASSWORD_DIGEST)) {
257                 addNonce(doc);
258                 addCreated(milliseconds, doc);
259             } else {
260                 hashed = false;
261             }
262         }
263     }
264     
265     /**
266      * Add the WSSE Namespace to this UT. The namespace is not added by default for
267      * efficiency purposes.
268      */
269     public void addWSSENamespace() {
270         WSSecurityUtil.setNamespace(element, WSConstants.WSSE_NS, WSConstants.WSSE_PREFIX);
271     }
272     
273     /**
274      * Add the WSU Namespace to this UT. The namespace is not added by default for
275      * efficiency purposes.
276      */
277     public void addWSUNamespace() {
278         WSSecurityUtil.setNamespace(element, WSConstants.WSU_NS, WSConstants.WSU_PREFIX);
279     }
280 
281     /**
282      * Creates and adds a Nonce element to this UsernameToken
283      */
284     public void addNonce(Document doc) {
285         if (elementNonce != null) {
286             return;
287         }
288         byte[] nonceValue = null;
289         try {
290             nonceValue = WSSecurityUtil.generateNonce(16);
291         } catch (WSSecurityException ex) {
292             LOG.debug(ex.getMessage(), ex);
293             return;
294         }
295         elementNonce = doc.createElementNS(WSConstants.WSSE_NS, "wsse:" + WSConstants.NONCE_LN);
296         elementNonce.appendChild(doc.createTextNode(Base64.encode(nonceValue)));
297         elementNonce.setAttributeNS(null, "EncodingType", BASE64_ENCODING);
298         element.appendChild(elementNonce);
299     }
300 
301     /**
302      * Creates and adds a Created element to this UsernameToken
303      */
304     public void addCreated(boolean milliseconds, Document doc) {
305         if (elementCreated != null) {
306             return;
307         }
308         DateFormat zulu = null;
309         if (milliseconds) {
310             zulu = new XmlSchemaDateFormat();
311         } else {
312             zulu = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
313             zulu.setTimeZone(TimeZone.getTimeZone("UTC"));
314         }
315         elementCreated = 
316             doc.createElementNS(
317                 WSConstants.WSU_NS, WSConstants.WSU_PREFIX + ":" + WSConstants.CREATED_LN
318             );
319         Date currentTime = new Date();
320         elementCreated.appendChild(doc.createTextNode(zulu.format(currentTime)));
321         element.appendChild(elementCreated);
322     }
323 
324     /**
325      * Adds and optionally creates a Salt element to this UsernameToken.
326      * 
327      * If the <code>saltValue</code> is <code>null</code> the the method
328      * generates a new salt. Otherwise it uses the the given value.
329      * 
330      * @param doc The Document for the UsernameToken
331      * @param saltValue The salt to add, if null generate a new salt value
332      * @param mac If <code>true</code> then an optionally generated value is
333      *            usable for a MAC
334      * @return Returns the added salt
335      */
336     public byte[] addSalt(Document doc, byte[] saltValue, boolean mac) {
337         if (saltValue == null) {
338             saltValue = generateSalt(mac);
339         }
340         elementSalt = 
341             doc.createElementNS(
342                 WSConstants.WSSE11_NS, WSConstants.WSSE11_PREFIX + ":" + WSConstants.SALT_LN
343             );
344         WSSecurityUtil.setNamespace(element, WSConstants.WSSE11_NS, WSConstants.WSSE11_PREFIX);
345         elementSalt.appendChild(doc.createTextNode(Base64.encode(saltValue)));
346         element.appendChild(elementSalt);
347         return saltValue;
348     }
349 
350     /**
351      * Creates and adds a Iteration element to this UsernameToken
352      */
353     public void addIteration(Document doc, int iteration) {
354         String text = "" + iteration;
355         elementIteration = 
356             doc.createElementNS(
357                 WSConstants.WSSE11_NS, WSConstants.WSSE11_PREFIX + ":" + WSConstants.ITERATION_LN
358             );
359         WSSecurityUtil.setNamespace(element, WSConstants.WSSE11_NS, WSConstants.WSSE11_PREFIX);
360         elementIteration.appendChild(doc.createTextNode(text));
361         element.appendChild(elementIteration);
362     }
363 
364     /**
365      * Get the user name.
366      * 
367      * @return the data from the user name element.
368      */
369     public String getName() {
370         return nodeString(elementUsername);
371     }
372 
373     /**
374      * Set the user name.
375      * 
376      * @param name sets a text node containing the use name into the user name
377      *             element.
378      */
379     public void setName(String name) {
380         Text node = getFirstNode(elementUsername);
381         node.setData(name);
382     }
383 
384     /**
385      * Get the nonce.
386      * 
387      * @return the data from the nonce element.
388      */
389     public String getNonce() {
390         return nodeString(elementNonce);
391     }
392 
393     /**
394      * Get the created timestamp.
395      * 
396      * @return the data from the created time element.
397      */
398     public String getCreated() {
399         return nodeString(elementCreated);
400     }
401 
402     /**
403      * Gets the password string. This is the password as it is in the password
404      * element of a username token. Thus it can be either plain text or the
405      * password digest value.
406      * 
407      * @return the password string or <code>null</code> if no such node exists.
408      */
409     public String getPassword() {
410         String password = nodeString(elementPassword);
411         // See WSS-219
412         if (password == null && elementPassword != null) {
413             return "";
414         }
415         return password;
416     }
417 
418     /**
419      * Get the Salt value of this UsernameToken.
420      * 
421      * @return Returns the binary Salt value or <code>null</code> if no Salt
422      *         value is available in the username token.
423      * @throws WSSecurityException
424      */
425     public byte[] getSalt() throws WSSecurityException {
426         String salt = nodeString(elementSalt);
427         if (salt != null) {
428             return Base64.decode(salt);
429         }
430         return null;
431     }
432 
433     /**
434      * Get the Iteration value of this UsernameToken.
435      * 
436      * @return Returns the Iteration value. If no Iteration was specified in the
437      *         username token the default value according to the specification
438      *         is returned.
439      */
440     public int getIteration() {
441         String iter = nodeString(elementIteration);
442         if (iter != null) {
443             return Integer.parseInt(iter);
444         }
445         return DEFAULT_ITERATION;
446     }
447 
448     /**
449      * Get the hashed indicator. If the indicator is <code>true> the password of the
450      * <code>UsernameToken</code> was encoded using {@link WSConstants#PASSWORD_DIGEST}
451      *
452      * @return the hashed indicator.
453      */
454     public boolean isHashed() {
455         return hashed;
456     }
457 
458     /**
459      * @return Returns the passwordType.
460      */
461     public String getPasswordType() {
462         return passwordType;
463     }
464 
465     /**
466      * Sets the password string. This function sets the password in the
467      * <code>UsernameToken</code> either as plain text or encodes the password
468      * according to the WS Security specifications, UsernameToken profile, into
469      * a password digest.
470      * 
471      * @param pwd the password to use
472      */
473     public void setPassword(String pwd) {
474         if (pwd == null) {
475             if (passwordType != null) {
476                 throw new IllegalArgumentException("pwd == null but a password is needed");
477             } else {
478                 // Ignore setting the password.
479                 return;
480             }
481         }
482         
483         rawPassword = pwd;             // enhancement by Alberto coletti
484         Text node = getFirstNode(elementPassword);
485         try {
486             if (hashed) {
487                 if (passwordsAreEncoded) {
488                     node.setData(doPasswordDigest(getNonce(), getCreated(), Base64.decode(pwd)));
489                 } else {
490                     node.setData(doPasswordDigest(getNonce(), getCreated(), pwd));
491                 }
492             } else {
493                 node.setData(pwd);
494             }
495             if (passwordType != null) {
496                 elementPassword.setAttributeNS(null, "Type", passwordType);
497             }
498         } catch (Exception e) {
499             if (DO_DEBUG) {
500                 LOG.debug(e.getMessage(), e);
501             }
502         }
503     }
504 
505     /**
506      * Set the raw (plain text) password used to compute secret key.
507      */
508     public void setRawPassword(RequestData data) throws WSSecurityException {
509         WSPasswordCallback pwCb = 
510             new WSPasswordCallback(
511                 getName(), getPassword(), getPasswordType(), 
512                 WSPasswordCallback.USERNAME_TOKEN, data
513             );
514         
515         if (data.getCallbackHandler() == null) {
516             LOG.debug("CallbackHandler is null");
517             throw new WSSecurityException(WSSecurityException.FAILED_AUTHENTICATION);
518         }
519         try {
520             data.getCallbackHandler().handle(new Callback[]{pwCb});
521         } catch (IOException e) {
522             if (LOG.isDebugEnabled()) {
523                 LOG.debug(e);
524             }
525             throw new WSSecurityException(
526                 WSSecurityException.FAILED_AUTHENTICATION, null, null, e
527             );
528         } catch (UnsupportedCallbackException e) {
529             if (LOG.isDebugEnabled()) {
530                 LOG.debug(e);
531             }
532             throw new WSSecurityException(
533                 WSSecurityException.FAILED_AUTHENTICATION, null, null, e
534             );
535         }
536         rawPassword = pwCb.getPassword();
537     }
538     
539     /**
540      * @param passwordsAreEncoded whether passwords are encoded
541      */
542     public void setPasswordsAreEncoded(boolean passwordsAreEncoded) {
543         this.passwordsAreEncoded = passwordsAreEncoded;
544     }
545     
546     /**
547      * @return whether passwords are encoded
548      */
549     public boolean getPasswordsAreEncoded() {
550         return passwordsAreEncoded;
551     }
552     
553     public static String doPasswordDigest(String nonce, String created, byte[] password) {
554         String passwdDigest = null;
555         try {
556             byte[] b1 = nonce != null ? Base64.decode(nonce) : new byte[0];
557             byte[] b2 = created != null ? created.getBytes("UTF-8") : new byte[0];
558             byte[] b3 = password;
559             byte[] b4 = new byte[b1.length + b2.length + b3.length];
560             int offset = 0;
561             System.arraycopy(b1, 0, b4, offset, b1.length);
562             offset += b1.length;
563             
564             System.arraycopy(b2, 0, b4, offset, b2.length);
565             offset += b2.length;
566 
567             System.arraycopy(b3, 0, b4, offset, b3.length);
568             
569             byte[] digestBytes = WSSecurityUtil.generateDigest(b4);
570             passwdDigest = Base64.encode(digestBytes);
571         } catch (Exception e) {
572             if (DO_DEBUG) {
573                 LOG.debug(e.getMessage(), e);
574             }
575         }
576         return passwdDigest;
577     }
578 
579     public static String doPasswordDigest(String nonce, String created, String password) {
580         String passwdDigest = null;
581         try {
582             passwdDigest = doPasswordDigest(nonce, created, password.getBytes("UTF-8"));
583         } catch (Exception e) {
584             if (DO_DEBUG) {
585                 LOG.debug(e.getMessage(), e);
586             }
587         }
588         return passwdDigest;
589     }
590 
591     /**
592      * Returns the first text node of an element.
593      * 
594      * @param e the element to get the node from
595      * @return the first text node or <code>null</code> if node is null or is
596      *         not a text node
597      */
598     private Text getFirstNode(Element e) {
599         Node node = e.getFirstChild();
600         return (node != null && Node.TEXT_NODE == node.getNodeType()) ? (Text) node : null;
601     }
602 
603     /**
604      * Returns the data of an element as String or null if either the the element
605      * does not contain a Text node or the node is empty.
606      * 
607      * @param e DOM element
608      * @return Element text node data as String
609      */
610     private String nodeString(Element e) {
611         if (e != null) {
612             Node node = e.getFirstChild();
613             StringBuilder builder = new StringBuilder();
614             boolean found = false;
615             while (node != null) {
616                 if (Node.TEXT_NODE == node.getNodeType()) {
617                     found = true;
618                     builder.append(((Text)node).getData());
619                 }
620                 node = node.getNextSibling();
621             }
622             
623             if (!found) {
624                 return null;
625             }
626             return builder.toString();
627         }
628         return null;
629     }
630 
631     /**
632      * Returns the dom element of this <code>UsernameToken</code> object.
633      * 
634      * @return the <code>wsse:UsernameToken</code> element
635      */
636     public Element getElement() {
637         return element;
638     }
639 
640     /**
641      * Returns the string representation of the token.
642      * 
643      * @return a XML string representation
644      */
645     public String toString() {
646         return DOM2Writer.nodeToString((Node)element);
647     }
648 
649     /**
650      * Gets the id.
651      * 
652      * @return the value of the <code>wsu:Id</code> attribute of this username
653      *         token
654      */
655     public String getID() {
656         return element.getAttributeNS(WSConstants.WSU_NS, "Id");
657     }
658 
659     /**
660      * Set the id of this username token.
661      * 
662      * @param id
663      *            the value for the <code>wsu:Id</code> attribute of this
664      *            username token
665      */
666     public void setID(String id) {
667         element.setAttributeNS(WSConstants.WSU_NS, WSConstants.WSU_PREFIX + ":Id", id);
668     }
669 
670     /**
671      * Gets the secret key as per WS-Trust spec. This method uses default setting
672      * to generate the secret key. These default values are suitable for .NET
673      * WSE.
674      * 
675      * @return a secret key constructed from information contained in this
676      *         username token
677      */
678     public byte[] getSecretKey() {
679         return getSecretKey(WSConstants.WSE_DERIVED_KEY_LEN, WSConstants.LABEL_FOR_DERIVED_KEY);
680     }
681     
682     /**
683      * Gets the secret key as per WS-Trust spec. This method uses default setting
684      * to generate the secret key. These default values are suitable for .NET
685      * WSE.
686      * 
687      * @return a secret key constructed from information contained in this
688      *         username token
689      */
690     public byte[] getSecretKey(int keylen) {
691         return getSecretKey(keylen, WSConstants.LABEL_FOR_DERIVED_KEY);
692     }
693 
694     /**
695      * Gets the secret key as per WS-Trust spec.
696      * 
697      * @param keylen How many bytes to generate for the key
698      * @param labelString the label used to generate the seed
699      * @return a secret key constructed from information contained in this
700      *         username token
701      */
702     public byte[] getSecretKey(int keylen, String labelString) {
703         byte[] key = null;
704         try {
705             Mac mac = Mac.getInstance("HMACSHA1");
706             byte[] password;
707             if (passwordsAreEncoded) {
708                 password = Base64.decode(rawPassword);
709             } else {
710                 password = rawPassword.getBytes("UTF-8"); // enhancement by Alberto Coletti
711             }
712             byte[] label = labelString.getBytes("UTF-8");
713             byte[] nonce = Base64.decode(getNonce());
714             byte[] created = getCreated().getBytes("UTF-8");
715             byte[] seed = new byte[label.length + nonce.length + created.length];
716 
717             int offset = 0;
718             System.arraycopy(label, 0, seed, offset, label.length);
719             offset += label.length;
720             
721             System.arraycopy(nonce, 0, seed, offset, nonce.length);
722             offset += nonce.length;
723 
724             System.arraycopy(created, 0, seed, offset, created.length);
725             
726             key = P_hash(password, seed, mac, keylen);
727 
728             if (LOG.isDebugEnabled()) {
729                 LOG.debug("label      :" + Base64.encode(label));
730                 LOG.debug("nonce      :" + Base64.encode(nonce));
731                 LOG.debug("created    :" + Base64.encode(created));
732                 LOG.debug("seed       :" + Base64.encode(seed));
733                 LOG.debug("Key        :" + Base64.encode(key));
734             }
735         } catch (Exception e) {
736             if (DO_DEBUG) {
737                 LOG.debug(e.getMessage(), e);
738             }
739             return null;
740         }
741         return key;
742     }
743     
744     
745     /**
746      * This static method generates a derived key as defined in WSS Username
747      * Token Profile.
748      * 
749      * @param password The password to include in the key generation
750      * @param salt The Salt value
751      * @param iteration The Iteration value. If zero (0) is given the method uses the
752      *                  default value
753      * @return Returns the derived key a byte array
754      * @throws WSSecurityException
755      */
756     public static byte[] generateDerivedKey(
757         byte[] password, 
758         byte[] salt, 
759         int iteration
760     ) throws WSSecurityException {
761         if (iteration == 0) {
762             iteration = DEFAULT_ITERATION;
763         }
764 
765         byte[] pwSalt = new byte[salt.length + password.length];
766         System.arraycopy(password, 0, pwSalt, 0, password.length);
767         System.arraycopy(salt, 0, pwSalt, password.length, salt.length);
768 
769         MessageDigest sha = null;
770         try {
771             sha = MessageDigest.getInstance("SHA1");
772         } catch (NoSuchAlgorithmException e) {
773             if (DO_DEBUG) {
774                 LOG.debug(e.getMessage(), e);
775             }
776             throw new WSSecurityException(
777                 WSSecurityException.FAILURE, "noSHA1availabe", null, e
778             );
779         }
780         //
781         // Make the first hash round with start value
782         //
783         byte[] k = sha.digest(pwSalt);
784         //
785         // Perform the 1st up to iteration-1 hash rounds
786         //
787         for (int i = 1; i < iteration; i++) {
788             k = sha.digest(k);
789         }
790         return k;
791     }
792     
793     /**
794      * This static method generates a derived key as defined in WSS Username
795      * Token Profile.
796      * 
797      * @param password The password to include in the key generation
798      * @param salt The Salt value
799      * @param iteration The Iteration value. If zero (0) is given the method uses the
800      *                  default value
801      * @return Returns the derived key a byte array
802      * @throws WSSecurityException
803      */
804     public static byte[] generateDerivedKey(
805         String password, 
806         byte[] salt, 
807         int iteration
808     ) throws WSSecurityException {
809         try {
810             return generateDerivedKey(password.getBytes("UTF-8"), salt, iteration);
811         } catch (final java.io.UnsupportedEncodingException e) {
812             if (DO_DEBUG) {
813                 LOG.debug(e.getMessage(), e);
814             }
815             throw new WSSecurityException("Unable to convert password to UTF-8", e);
816         }
817     }
818     
819     
820     /**
821      * This method gets a derived key as defined in WSS Username Token Profile.
822      * 
823      * @return Returns the derived key as a byte array
824      * @throws WSSecurityException
825      */
826     public byte[] getDerivedKey() throws WSSecurityException {
827         if (rawPassword == null || !bspCompliantDerivedKey) {
828             LOG.debug("The raw password was null or the Username Token is not BSP compliant");
829             throw new WSSecurityException(WSSecurityException.FAILED_AUTHENTICATION);
830         }
831         int iteration = getIteration();
832         byte[] salt = getSalt();
833         if (passwordsAreEncoded) {
834             return generateDerivedKey(Base64.decode(rawPassword), salt, iteration);
835         } else {
836             return generateDerivedKey(rawPassword, salt, iteration);
837         }
838     }
839     
840     /**
841      * Return whether the UsernameToken represented by this class is to be used
842      * for key derivation as per the UsernameToken Profile 1.1. It does this by
843      * checking that the username token has salt and iteration values.
844      * 
845      * @throws WSSecurityException
846      */
847     public boolean isDerivedKey() throws WSSecurityException {
848         if (elementSalt != null && elementIteration != null) {
849             return true;
850         }
851         return false;
852     }
853     
854     /**
855      * Create a WSUsernameTokenPrincipal from this UsernameToken object
856      */
857     public Principal createPrincipal() {
858         WSUsernameTokenPrincipal principal = 
859             new WSUsernameTokenPrincipal(getName(), isHashed());
860         principal.setNonce(getNonce());
861         principal.setPassword(getPassword());
862         principal.setCreatedTime(getCreated());
863         return principal;
864     }
865     
866     /**
867      * This static method generates a 128 bit salt value as defined in WSS
868      * Username Token Profile.
869      * 
870      * @param useForMac If <code>true</code> define the Salt for use in a MAC
871      * @return Returns the 128 bit salt value as byte array
872      */
873     public static byte[] generateSalt(boolean useForMac) {
874         byte[] saltValue = null;
875         try {
876             saltValue = WSSecurityUtil.generateNonce(16);
877         } catch (WSSecurityException ex) {
878             LOG.debug(ex.getMessage(), ex);
879             return null;
880         }
881         if (useForMac) {
882             saltValue[0] = 0x01;
883         } else {
884             saltValue[0] = 0x02;
885         }
886         return saltValue;
887     }
888 
889     @Override
890     public int hashCode() {
891         int result = 17;
892         String username = getName();
893         if (username != null) {
894             result = 31 * result + username.hashCode();
895         }
896         String password = getPassword();
897         if (password != null) {
898             result = 31 * result + password.hashCode();
899         }
900         String passwordType = getPasswordType();
901         if (passwordType != null) {
902             result = 31 * result + passwordType.hashCode();
903         }
904         String nonce = getNonce();
905         if (nonce != null) {
906             result = 31 * result + nonce.hashCode();
907         }
908         String created = getCreated();
909         if (created != null) {
910             result = 31 * result + created.hashCode();
911         }
912         try {
913             byte[] salt = getSalt();
914             if (salt != null) {
915                 result = 31 * result + Arrays.hashCode(salt);
916             }
917         } catch (WSSecurityException ex) {
918             if (LOG.isDebugEnabled()) {
919                 LOG.debug(ex.getMessage(), ex);
920             }
921         }
922         result = 31 * result + Integer.valueOf(getIteration()).hashCode();
923         
924         return result;
925     }
926     
927     @Override
928     public boolean equals(Object object) {
929         if (!(object instanceof UsernameToken)) {
930             return false;
931         }
932         UsernameToken usernameToken = (UsernameToken)object;
933         if (!compare(usernameToken.getName(), getName())) {
934             return false;
935         }
936         if (!compare(usernameToken.getPassword(), getPassword())) {
937             return false;
938         }
939         if (!compare(usernameToken.getPasswordType(), getPasswordType())) {
940             return false;
941         }
942         if (!compare(usernameToken.getNonce(), getNonce())) {
943             return false;
944         }
945         if (!compare(usernameToken.getCreated(), getCreated())) {
946             return false;
947         }
948         try {
949             byte[] salt = usernameToken.getSalt();
950             if (!Arrays.equals(salt, getSalt())) {
951                 return false;
952             }
953         } catch (WSSecurityException ex) {
954             if (LOG.isDebugEnabled()) {
955                 LOG.debug(ex.getMessage(), ex);
956             }
957         }
958         int iteration = usernameToken.getIteration();
959         if (iteration != getIteration()) {
960             return false;
961         }
962         return true;
963     }
964     
965     private boolean compare(String item1, String item2) {
966         if (item1 == null && item2 != null) { 
967             return false;
968         } else if (item1 != null && !item1.equals(item2)) {
969             return false;
970         }
971         return true;
972     }
973     
974     /**
975      * P_hash as defined in RFC 2246 for TLS.
976      * 
977      * @param secret is the key for the HMAC
978      * @param seed the seed value to start the generation - A(0)
979      * @param mac the HMAC algorithm
980      * @param required number of bytes to generate
981      * @return a byte array that contains a secret key
982      * @throws Exception
983      */
984     private static byte[] P_hash(
985         byte[] secret, 
986         byte[] seed, 
987         Mac mac, 
988         int required
989     ) throws Exception {
990         byte[] out = new byte[required];
991         int offset = 0, tocpy;
992         byte[] a, tmp;
993         //
994         // a(0) is the seed
995         //
996         a = seed;
997         SecretKeySpec key = new SecretKeySpec(secret, "HMACSHA1");
998         mac.init(key);
999         while (required > 0) {
1000             mac.update(a);
1001             a = mac.doFinal();
1002             mac.update(a);
1003             mac.update(seed);
1004             tmp = mac.doFinal();
1005             tocpy = min(required, tmp.length);
1006             System.arraycopy(tmp, 0, out, offset, tocpy);
1007             offset += tocpy;
1008             required -= tocpy;
1009         }
1010         return out;
1011     }
1012 
1013     /**
1014      * helper method.
1015      *
1016      * @param a
1017      * @param b
1018      * @return
1019      */
1020     private static int min(int a, int b) {
1021         return (a > b) ? b : a;
1022     }
1023     
1024     /**
1025      * A method to check that the UsernameToken is compliant with the BSP spec.
1026      * @throws WSSecurityException
1027      */
1028     private void checkBSPCompliance() throws WSSecurityException {
1029         List<Element> passwordElements = 
1030             WSSecurityUtil.getDirectChildElements(
1031                 element, WSConstants.PASSWORD_LN, WSConstants.WSSE_NS
1032             );
1033         // We can only have one password element
1034         if (passwordElements.size() > 1) {
1035             if (LOG.isDebugEnabled()) {
1036                 LOG.debug("The Username Token had more than one password element");
1037             }
1038             throw new WSSecurityException(
1039                 WSSecurityException.INVALID_SECURITY_TOKEN, "badUsernameToken"
1040             );
1041         }
1042         
1043         // We must have a password type
1044         if (passwordElements.size() == 1) {
1045             Element passwordChild = passwordElements.get(0);
1046             String type = passwordChild.getAttributeNS(null, WSConstants.PASSWORD_TYPE_ATTR);
1047             if (type == null || "".equals(type)) {
1048                 if (LOG.isDebugEnabled()) {
1049                     LOG.debug("The Username Token password does not have a Type attribute");
1050                 }
1051                 throw new WSSecurityException(
1052                     WSSecurityException.INVALID_SECURITY_TOKEN, "badUsernameToken"
1053                 );
1054             }
1055         }
1056         
1057         if (elementSalt == null) {
1058             // We must have a salt element to use this token for a derived key
1059             bspCompliantDerivedKey = false;
1060         }
1061         if (elementIteration == null) {
1062             // we must have an iteration element to use this token for a derived key
1063             bspCompliantDerivedKey = false;
1064         } else {
1065             String iter = nodeString(elementIteration);
1066             if (iter == null || Integer.parseInt(iter) < 1000) {
1067                 bspCompliantDerivedKey = false;
1068             }
1069         }
1070         
1071         List<Element> createdElements = 
1072             WSSecurityUtil.getDirectChildElements(
1073                 element, WSConstants.CREATED_LN, WSConstants.WSU_NS
1074             );
1075         // We can only have one created element
1076         if (createdElements.size() > 1) {
1077             if (LOG.isDebugEnabled()) {
1078                 LOG.debug("The Username Token has more than one created element");
1079             }
1080             throw new WSSecurityException(
1081                 WSSecurityException.INVALID_SECURITY_TOKEN, "badUsernameToken"
1082             );
1083         }
1084         
1085         List<Element> nonceElements = 
1086             WSSecurityUtil.getDirectChildElements(
1087                 element, WSConstants.NONCE_LN, WSConstants.WSSE_NS
1088             );
1089         // We can only have one nonce element
1090         if (nonceElements.size() > 1) {
1091             if (LOG.isDebugEnabled()) {
1092                 LOG.debug("The Username Token has more than one nonce element");
1093             }
1094             throw new WSSecurityException(
1095                 WSSecurityException.INVALID_SECURITY_TOKEN, "badUsernameToken"
1096             );
1097         }
1098         
1099         if (nonceElements.size() == 1) {
1100             Element nonce = nonceElements.get(0);
1101             String encodingType = nonce.getAttribute("EncodingType");
1102             // Encoding Type must be equal to Base64Binary
1103             if (encodingType == null || "".equals(encodingType)
1104                 || !BinarySecurity.BASE64_ENCODING.equals(encodingType)) {
1105                 if (LOG.isDebugEnabled()) {
1106                     LOG.debug("The Username Token's nonce element has a bad encoding type");
1107                 }
1108                 throw new WSSecurityException(
1109                     WSSecurityException.INVALID_SECURITY_TOKEN, 
1110                     "badUsernameToken" 
1111                 );
1112             }
1113         }
1114     }
1115 }