View Javadoc
1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.wss4j.common.util;
21  
22  import java.io.IOException;
23  import java.nio.charset.StandardCharsets;
24  import java.security.MessageDigest;
25  import java.security.NoSuchAlgorithmException;
26  
27  import javax.security.auth.callback.Callback;
28  import javax.security.auth.callback.CallbackHandler;
29  import javax.security.auth.callback.UnsupportedCallbackException;
30  
31  import org.apache.wss4j.common.ext.WSPasswordCallback;
32  import org.apache.wss4j.common.ext.WSSecurityException;
33  import org.apache.xml.security.stax.ext.XMLSecurityConstants;
34  
35  public final class UsernameTokenUtil {
36      public static final int DEFAULT_ITERATION = 1000;
37  
38      private static final org.slf4j.Logger LOG =
39              org.slf4j.LoggerFactory.getLogger(UsernameTokenUtil.class);
40  
41      private UsernameTokenUtil() {
42          // complete
43      }
44  
45      /**
46       * This static method generates a derived key as defined in WSS Username
47       * Token Profile.
48       *
49       * @param password The password to include in the key generation
50       * @param salt The Salt value
51       * @param iteration The Iteration value. If zero (0) is given the method uses the
52       *                  default value
53       * @return Returns the derived key a byte array
54       * @throws WSSecurityException
55       */
56      public static byte[] generateDerivedKey(
57          byte[] password,
58          byte[] salt,
59          int iteration
60      ) throws WSSecurityException {
61          byte[] pwSalt = new byte[salt.length + password.length];
62          System.arraycopy(password, 0, pwSalt, 0, password.length);
63          System.arraycopy(salt, 0, pwSalt, password.length, salt.length);
64  
65          MessageDigest sha = null;
66          try {
67              sha = MessageDigest.getInstance("SHA1");
68          } catch (NoSuchAlgorithmException e) {
69              LOG.debug(e.getMessage(), e);
70              throw new WSSecurityException(
71                  WSSecurityException.ErrorCode.FAILURE, e, "decoding.general"
72              );
73          }
74          //
75          // Make the first hash round with start value
76          //
77          byte[] k = sha.digest(pwSalt);
78          //
79          // Perform the 1st up to iteration-1 hash rounds
80          //
81          int iter = iteration;
82          if (iter <= 0) {
83              iter = DEFAULT_ITERATION;
84          }
85          for (int i = 1; i < iter; i++) {
86              k = sha.digest(k);
87          }
88          return k;
89      }
90  
91      /**
92       * This static method generates a derived key as defined in WSS Username
93       * Token Profile.
94       *
95       * @param password The password to include in the key generation
96       * @param salt The Salt value
97       * @param iteration The Iteration value. If zero (0) is given the method uses the
98       *                  default value
99       * @return Returns the derived key a byte array
100      * @throws WSSecurityException
101      */
102     public static byte[] generateDerivedKey(
103         String password,
104         byte[] salt,
105         int iteration
106     ) throws WSSecurityException {
107         return generateDerivedKey(password.getBytes(StandardCharsets.UTF_8), salt, iteration);
108     }
109 
110     /**
111      * This static method generates a 128 bit salt value as defined in WSS
112      * Username Token Profile.
113      *
114      * @param useForMac If <code>true</code> define the Salt for use in a MAC
115      * @return Returns the 128 bit salt value as byte array
116      */
117     public static byte[] generateSalt(boolean useForMac) {
118         byte[] saltValue = null;
119         try {
120             saltValue = generateNonce(16);
121         } catch (WSSecurityException ex) {
122             LOG.debug(ex.getMessage(), ex);
123             return new byte[0];
124         }
125         if (useForMac) {
126             saltValue[0] = 0x01;
127         } else {
128             saltValue[0] = 0x02;
129         }
130         return saltValue;
131     }
132 
133     /**
134      * Generate a nonce of the given length using a secure random algorithm. The SecureRandom
135      * instance that backs this method is cached for efficiency.
136      *
137      * @return a nonce of the given length
138      * @throws WSSecurityException
139      */
140     public static byte[] generateNonce(int length) throws WSSecurityException {
141         try {
142             return XMLSecurityConstants.generateBytes(length);
143         } catch (Exception ex) {
144             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex,
145                     "empty", new Object[] {"Error in generating nonce of length " + length}
146             );
147         }
148     }
149 
150     public static String doPasswordDigest(byte[] nonce, String created, String password) throws WSSecurityException {
151         return doPasswordDigest(nonce, created, password.getBytes(StandardCharsets.UTF_8));
152     }
153 
154     public static String doPasswordDigest(byte[] nonce, String created, byte[] password) throws WSSecurityException {
155         byte[] digestBytes = doRawPasswordDigest(nonce, created, password);
156         return org.apache.xml.security.utils.XMLUtils.encodeToString(digestBytes);
157     }
158 
159     public static byte[] doRawPasswordDigest(byte[] nonce, String created, byte[] password) throws WSSecurityException {
160         try {
161             byte[] b1 = nonce != null ? nonce : new byte[0];
162             byte[] b2 = created != null ? created.getBytes(StandardCharsets.UTF_8) : new byte[0];
163             byte[] b3 = password;
164             byte[] b4 = new byte[b1.length + b2.length + b3.length];
165             int offset = 0;
166             System.arraycopy(b1, 0, b4, offset, b1.length);
167             offset += b1.length;
168 
169             System.arraycopy(b2, 0, b4, offset, b2.length);
170             offset += b2.length;
171 
172             System.arraycopy(b3, 0, b4, offset, b3.length);
173 
174             return KeyUtils.generateDigest(b4);
175         } catch (Exception e) {
176             LOG.debug(e.getMessage(), e);
177             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, e, "decoding.general");
178         }
179     }
180 
181     /**
182      * Get the raw (plain text) password used to compute secret key.
183      */
184     public static String getRawPassword(CallbackHandler callbackHandler, String username,
185                                         String password, String passwordType) throws WSSecurityException {
186         if (callbackHandler == null) {
187             LOG.debug("CallbackHandler is null");
188             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION);
189         }
190 
191         WSPasswordCallback pwCb =
192             new WSPasswordCallback(
193                 username, password, passwordType, WSPasswordCallback.USERNAME_TOKEN
194             );
195         try {
196             callbackHandler.handle(new Callback[]{pwCb});
197         } catch (IOException | UnsupportedCallbackException e) {
198             LOG.debug(e.getMessage(), e);
199             throw new WSSecurityException(
200                 WSSecurityException.ErrorCode.FAILED_AUTHENTICATION, e
201             );
202         }
203         return pwCb.getPassword();
204     }
205 }