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