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  package org.apache.myfaces.application.viewstate;
20  
21  import java.security.NoSuchAlgorithmException;
22  import java.security.NoSuchProviderException;
23  import java.security.SecureRandom;
24  import java.util.Queue;
25  import java.util.concurrent.ConcurrentLinkedQueue;
26  import java.util.logging.Level;
27  import java.util.logging.Logger;
28  
29  /**
30   * NOTE: Class taken from tomcat 7 org.apache.catalina.util.SessionIdGenerator
31   * and used here as an alternative for server side state token encryption.
32   * 
33   */
34  class SessionIdGenerator
35  {
36  
37      private static Logger log = Logger.getLogger(SessionIdGenerator.class.getName());
38  
39      /**
40       * Queue of random number generator objects to be used when creating session
41       * identifiers. If the queue is empty when a random number generator is
42       * required, a new random number generator object is created. This is
43       * designed this way since random number generators use a sync to make them
44       * thread-safe and the sync makes using a a single object slow(er).
45       */
46      private Queue<SecureRandom> randoms =
47              new ConcurrentLinkedQueue<SecureRandom>();
48      /**
49       * The Java class name of the secure random number generator class to be
50       * used when generating session identifiers. The random number generator
51       * class must be self-seeding and have a zero-argument constructor. If not
52       * specified, an instance of {@link SecureRandom} will be generated.
53       */
54      private String secureRandomClass = null;
55      /**
56       * The name of the algorithm to use to create instances of
57       * {@link SecureRandom} which are used to generate session IDs. If no
58       * algorithm is specified, SHA1PRNG is used. To use the platform default
59       * (which may be SHA1PRNG), specify the empty string. If an invalid
60       * algorithm and/or provider is specified the {@link SecureRandom} instances
61       * will be created using the defaults. If that fails, the {@link
62       * SecureRandom} instances will be created using platform defaults.
63       */
64      private String secureRandomAlgorithm = "SHA1PRNG";
65      /**
66       * The name of the provider to use to create instances of
67       * {@link SecureRandom} which are used to generate session IDs. If no
68       * algorithm is specified the of SHA1PRNG default is used. If an invalid
69       * algorithm and/or provider is specified the {@link SecureRandom} instances
70       * will be created using the defaults. If that fails, the {@link
71       * SecureRandom} instances will be created using platform defaults.
72       */
73      private String secureRandomProvider = null;
74      /**
75       * Node identifier when in a cluster. Defaults to the empty string.
76       */
77      private String jvmRoute = "";
78      /**
79       * Number of bytes in a session ID. Defaults to 16.
80       */
81      private int sessionIdLength = 16;
82  
83      /**
84       * Specify a non-default @{link {@link SecureRandom} implementation to use.
85       *
86       * @param secureRandomClass The fully-qualified class name
87       */
88      public void setSecureRandomClass(String secureRandomClass)
89      {
90          this.secureRandomClass = secureRandomClass;
91      }
92  
93      /**
94       * Specify a non-default algorithm to use to generate random numbers.
95       *
96       * @param secureRandomAlgorithm The name of the algorithm
97       */
98      public void setSecureRandomAlgorithm(String secureRandomAlgorithm)
99      {
100         this.secureRandomAlgorithm = secureRandomAlgorithm;
101     }
102 
103     /**
104      * Specify a non-default provider to use to generate random numbers.
105      *
106      * @param secureRandomProvider The name of the provider
107      */
108     public void setSecureRandomProvider(String secureRandomProvider)
109     {
110         this.secureRandomProvider = secureRandomProvider;
111     }
112 
113     /**
114      * Specify the node identifier associated with this node which will be
115      * included in the generated session ID.
116      *
117      * @param jvmRoute The node identifier
118      */
119     public void setJvmRoute(String jvmRoute)
120     {
121         this.jvmRoute = jvmRoute;
122     }
123 
124     /**
125      * Specify the number of bytes for a session ID
126      *
127      * @param sessionIdLength Number of bytes
128      */
129     public void setSessionIdLength(int sessionIdLength)
130     {
131         this.sessionIdLength = sessionIdLength;
132     }
133 
134     /**
135      * Generate and return a new session identifier.
136      */
137     public String generateSessionId()
138     {
139 
140         byte random[] = new byte[16];
141 
142         // Render the result as a String of hexadecimal digits
143         StringBuilder buffer = new StringBuilder();
144 
145         int resultLenBytes = 0;
146 
147         while (resultLenBytes < sessionIdLength)
148         {
149             getRandomBytes(random);
150             for (int j = 0;
151                     j < random.length && resultLenBytes < sessionIdLength;
152                     j++)
153             {
154                 byte b1 = (byte) ((random[j] & 0xf0) >> 4);
155                 byte b2 = (byte) (random[j] & 0x0f);
156                 if (b1 < 10)
157                 {
158                     buffer.append((char) ('0' + b1));
159                 }
160                 else
161                 {
162                     buffer.append((char) ('A' + (b1 - 10)));
163                 }
164                 if (b2 < 10)
165                 {
166                     buffer.append((char) ('0' + b2));
167                 }
168                 else
169                 {
170                     buffer.append((char) ('A' + (b2 - 10)));
171                 }
172                 resultLenBytes++;
173             }
174         }
175 
176         if (jvmRoute != null && jvmRoute.length() > 0)
177         {
178             buffer.append('.').append(jvmRoute);
179         }
180 
181         return buffer.toString();
182     }
183 
184     public void getRandomBytes(byte bytes[])
185     {
186         SecureRandom random = randoms.poll();
187         if (random == null)
188         {
189             random = createSecureRandom();
190         }
191         random.nextBytes(bytes);
192         randoms.add(random);
193     }
194 
195     /**
196      * Create a new random number generator instance we should use for
197      * generating session identifiers.
198      */
199     private SecureRandom createSecureRandom()
200     {
201 
202         SecureRandom result = null;
203 
204         long t1 = System.currentTimeMillis();
205         if (secureRandomClass != null)
206         {
207             try
208             {
209                 // Construct and seed a new random number generator
210                 Class<?> clazz = Class.forName(secureRandomClass);
211                 result = (SecureRandom) clazz.newInstance();
212             }
213             catch (Exception e)
214             {
215                 log.log(Level.SEVERE, "Exception initializing random number generator of class "+ 
216                         secureRandomClass + ". Falling back to java.secure.SecureRandom", e);
217             }
218         }
219 
220         if (result == null)
221         {
222             // No secureRandomClass or creation failed. Use SecureRandom.
223             try
224             {
225                 if (secureRandomProvider != null
226                         && secureRandomProvider.length() > 0)
227                 {
228                     result = SecureRandom.getInstance(secureRandomAlgorithm,
229                             secureRandomProvider);
230                 }
231                 else
232                 {
233                     if (secureRandomAlgorithm != null
234                             && secureRandomAlgorithm.length() > 0)
235                     {
236                         result = SecureRandom.getInstance(secureRandomAlgorithm);
237                     }
238                 }
239             }
240             catch (NoSuchAlgorithmException e)
241             {
242                 log.log(Level.SEVERE, "Exception initializing random number generator using algorithm: "+
243                         secureRandomAlgorithm, e);
244             }
245             catch (NoSuchProviderException e)
246             {
247                 log.log(Level.SEVERE, "Exception initializing random number generator using provider: " + 
248                         secureRandomProvider, e);
249             }
250         }
251 
252         if (result == null)
253         {
254             // Invalid provider / algorithm
255             try
256             {
257                 result = SecureRandom.getInstance("SHA1PRNG");
258             }
259             catch (NoSuchAlgorithmException e)
260             {
261                 log.log(Level.SEVERE, "Invalid provider / algoritm SHA1PRNG for generate secure random token", e);
262             }
263         }
264 
265         if (result == null)
266         {
267             // Nothing works - use platform default
268             result = new SecureRandom();
269         }
270 
271         // Force seeding to take place
272         result.nextInt();
273 
274         long t2 = System.currentTimeMillis();
275         if ((t2 - t1) > 100)
276         {
277             if (log.isLoggable(Level.FINEST))
278             {
279                 log.info("Creation of SecureRandom instance for session ID generation using ["
280                         +result.getAlgorithm()+"] took ["+Long.valueOf(t2 - t1)+"] milliseconds.");
281             }
282         }
283         return result;
284     }
285 }