1 /***
2 *
3 * Copyright 2003-2005 Core Developers Network Ltd.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 package org.codehaus.wadi.core.manager;
87
88 import org.apache.commons.logging.Log;
89 import org.apache.commons.logging.LogFactory;
90
91 import java.io.DataInputStream;
92 import java.io.File;
93 import java.io.FileInputStream;
94 import java.io.IOException;
95 import java.security.AccessController;
96 import java.security.MessageDigest;
97 import java.security.NoSuchAlgorithmException;
98 import java.security.PrivilegedAction;
99 import java.util.Random;
100
101 /***
102 * An IdGenerator borrowed from Tomcat
103 *
104 * @author <a href="mailto:jules@coredevelopers.net">Jules Gosnell</a>
105 * @version $Revision: 2285 $
106 */
107 public class TomcatSessionIdFactory
108 implements SessionIdFactory
109 {
110 protected final Log _log = LogFactory.getLog(getClass());
111
112 public String create() {
113 String id=generateSessionId();
114 if (log.isTraceEnabled()) log.trace("generated: "+id);
115 return id;
116 }
117
118 public int getSessionIdLength() {
119 return 32;
120 }
121
122 public void setSessionIdLength(int l) {
123 if (l!=getSessionIdLength())
124 if (_log.isWarnEnabled()) _log.warn("session id length is not a writeable attribute - ignoring new setting: " + l);
125 }
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153 protected final Log log=LogFactory.getLog(getClass());
154
155 class StringManager
156 {
157 String getString(String s){return s;}
158 String getString(String s, String a){return "["+a+"]: "+s;}
159 }
160
161 protected StringManager sm=new StringManager();
162
163 class Support
164 {
165 void firePropertyChange(String s, Object oldVal, Object newVal){}
166 }
167
168 protected Support support=new Support();
169
170
171
172
173
174 protected DataInputStream randomIS=null;
175 protected String devRandomSource="/dev/urandom";
176
177 /***
178 * The default message digest algorithm to use if we cannot use
179 * the requested one.
180 */
181 protected static final String DEFAULT_ALGORITHM = "MD5";
182
183 /***
184 * The number of random bytes to include when generating a
185 * session identifier.
186 */
187 protected static final int SESSION_ID_BYTES = 16;
188
189 /***
190 * The message digest algorithm to be used when generating session
191 * identifiers. This must be an algorithm supported by the
192 * <code>java.security.MessageDigest</code> class on your platform.
193 */
194 protected String algorithm = DEFAULT_ALGORITHM;
195
196 /***
197 * Return the MessageDigest implementation to be used when
198 * creating session identifiers.
199 */
200 protected MessageDigest digest = null;
201
202 /***
203 * A random number generator to use when generating session identifiers.
204 */
205 protected Random random = null;
206
207 /***
208 * The Java class name of the random number generator class to be used
209 * when generating session identifiers.
210 */
211 protected String randomClass = "java.security.SecureRandom";
212
213 /***
214 * A String initialization parameter used to increase the entropy of
215 * the initialization of our random number generator.
216 */
217 protected String entropy = null;
218
219 /***
220 * Return the entropy increaser value, or compute a semi-useful value
221 * if this String has not yet been set.
222 */
223 public String getEntropy() {
224
225
226 if (this.entropy == null)
227 setEntropy(this.toString());
228
229 return (this.entropy);
230
231 }
232
233 /***
234 * Set the entropy increaser value.
235 *
236 * @param entropy The new entropy increaser value
237 */
238 public void setEntropy(String entropy) {
239
240 String oldEntropy = entropy;
241 this.entropy = entropy;
242 support.firePropertyChange("entropy", oldEntropy, this.entropy);
243
244 }
245
246 /***
247 * Return the random number generator instance we should use for
248 * generating session identifiers. If there is no such generator
249 * currently defined, construct and seed a new one.
250 */
251 public synchronized Random getRandom() {
252 if (this.random == null) {
253 synchronized (this) {
254 if (this.random == null) {
255
256 long seed = System.currentTimeMillis();
257
258 char entropy[] = getEntropy().toCharArray();
259 for (int i = 0; i < entropy.length; i++) {
260 long update = ((byte) entropy[i]) << ((i % 8) * 8);
261 seed ^= update;
262 }
263 try {
264
265 Class clazz = Class.forName(randomClass);
266 this.random = (Random) clazz.newInstance();
267 this.random.setSeed(seed);
268 } catch (Exception e) {
269
270 log.error(sm.getString("managerBase.random", randomClass), e);
271 this.random = new java.util.Random();
272 this.random.setSeed(seed);
273 }
274
275
276
277
278 }
279 }
280 }
281
282 return (this.random);
283
284 }
285
286 private class PrivilegedSetRandomFile implements PrivilegedAction{
287
288 public Object run(){
289 try {
290 File f=new File( devRandomSource );
291 if( ! f.exists() ) return null;
292 randomIS= new DataInputStream( new FileInputStream(f));
293 randomIS.readLong();
294
295
296 return randomIS;
297 } catch (IOException ex){
298 return null;
299 }
300 }
301 }
302
303 /*** Use /dev/random-type special device. This is new code, but may reduce the
304 * big delay in generating the random.
305 *
306 * You must specify a path to a random generator file. Use /dev/urandom
307 * for linux ( or similar ) systems. Use /dev/random for maximum security
308 * ( it may block if not enough "random" exist ). You can also use
309 * a pipe that generates random.
310 *
311 * The code will check if the file exists, and default to java Random
312 * if not found. There is a significant performance difference, very
313 * visible on the first call to getSession ( like in the first JSP )
314 * - so use it if available.
315 */
316 public void setRandomFile( String s ) {
317
318
319 if (System.getSecurityManager() != null){
320 randomIS = (DataInputStream)AccessController.doPrivileged(new PrivilegedSetRandomFile());
321 } else {
322 try{
323 devRandomSource=s;
324 File f=new File( devRandomSource );
325 if( ! f.exists() ) return;
326 randomIS= new DataInputStream( new FileInputStream(f));
327 randomIS.readLong();
328
329
330 } catch( IOException ex ) {
331 randomIS=null;
332 }
333 }
334 }
335
336 /***
337 * Generate and return a new session identifier.
338 */
339 protected synchronized String generateSessionId() {
340 byte bytes[] = new byte[SESSION_ID_BYTES];
341 getRandomBytes( bytes );
342 bytes = getDigest().digest(bytes);
343
344
345 StringBuffer result = new StringBuffer();
346 for (int i = 0; i < bytes.length; i++) {
347 byte b1 = (byte) ((bytes[i] & 0xf0) >> 4);
348 byte b2 = (byte) (bytes[i] & 0x0f);
349 if (b1 < 10)
350 result.append((char) ('0' + b1));
351 else
352 result.append((char) ('A' + (b1 - 10)));
353 if (b2 < 10)
354 result.append((char) ('0' + b2));
355 else
356 result.append((char) ('A' + (b2 - 10)));
357 }
358 return (result.toString());
359
360 }
361
362 /***
363 * Return the MessageDigest object to be used for calculating
364 * session identifiers. If none has been created yet, initialize
365 * one the first time this method is called.
366 */
367 public synchronized MessageDigest getDigest() {
368
369 if (this.digest == null) {
370
371
372
373 try {
374 this.digest = MessageDigest.getInstance(algorithm);
375 } catch (NoSuchAlgorithmException e) {
376 log.error(sm.getString("managerBase.digest", algorithm), e);
377 try {
378 this.digest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
379 } catch (NoSuchAlgorithmException f) {
380 log.error(sm.getString("managerBase.digest", DEFAULT_ALGORITHM), e);
381 this.digest = null;
382 }
383 }
384
385
386
387
388
389 }
390
391 return (this.digest);
392
393 }
394
395 protected void getRandomBytes( byte bytes[] ) {
396
397 if( devRandomSource!=null && randomIS==null ) {
398 setRandomFile( devRandomSource );
399 }
400 if(randomIS!=null ) {
401 try {
402 int len=randomIS.read( bytes );
403 if( len==bytes.length ) {
404 return;
405 }
406
407
408 } catch( Exception ex ) {
409 }
410 devRandomSource=null;
411 randomIS=null;
412 }
413
414 getRandom().nextBytes(bytes);
415 }
416 }