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 }