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