View Javadoc

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  // TODO - how do I license this file ?
19  
20  /*
21   *
22   * ====================================================================
23   *
24   * The Apache Software License, Version 1.1
25   *
26   * Copyright (c) 1999 The Apache Software Foundation.  All rights
27   * reserved.
28   *
29   * Redistribution and use in source and binary forms, with or without
30   * modification, are permitted provided that the following conditions
31   * are met:
32   *
33   * 1. Redistributions of source code must retain the above copyright
34   *    notice, this list of conditions and the following disclaimer.
35   *
36   * 2. Redistributions in binary form must reproduce the above copyright
37   *    notice, this list of conditions and the following disclaimer in
38   *    the documentation and/or other materials provided with the
39   *    distribution.
40   *
41   * 3. The end-user documentation included with the redistribution, if
42   *    any, must include the following acknowlegement:
43   *       "This product includes software developed by the
44   *        Apache Software Foundation (http://www.apache.org/)."
45   *    Alternately, this acknowlegement may appear in the software itself,
46   *    if and wherever such third-party acknowlegements normally appear.
47   *
48   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
49   *    Foundation" must not be used to endorse or promote products derived
50   *    from this software without prior written permission. For written
51   *    permission, please contact apache@apache.org.
52   *
53   * 5. Products derived from this software may not be called "Apache"
54   *    nor may "Apache" appear in their names without prior written
55   *    permission of the Apache Group.
56   *
57   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
58   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
59   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
60   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
61   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
62   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
63   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
64   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
65   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
66   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
67   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
68   * SUCH DAMAGE.
69   * ====================================================================
70   *
71   * This software consists of voluntary contributions made by many
72   * individuals on behalf of the Apache Software Foundation.  For more
73   * information on the Apache Software Foundation, please see
74   * <http://www.apache.org/>.
75   *
76   * [Additional notices, if required by prior licensing conditions]
77   *
78   */
79  
80  // this class has been put together from code taken from Tomcat5
81  // org.apache.catalina.session.ManagerBase. When I have the time, I
82  // will invetigate UID generation and write my own generator...
83  
84  // How hard can it be to generate a secure session id ? :-)
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 //   // we should be able to better than this - can't we work out the
128 //   // size of the ID ?
129 //   public String
130 //     getId(String idId, String idRoute)
131 //   {
132 //     return idId+"."+idRoute;
133 //   }
134 
135 //   public String
136 //     getIdId(String id)
137 //     {
138 //       int index=id.indexOf('.');
139 //       return index<0?id:id.substring(0,index);
140 //     }
141 
142 //   public String
143 //     getIdRoute(String id)
144 //     {
145 //       int index=id.indexOf('.');
146 //       return index<0?null:id.substring(index+1,id.length());
147 //     }
148 
149   //------------------------------------------------
150   // integration layer..
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){}	// TODO - do we really need this ?
166   }
167 
168   protected Support support=new Support();
169 
170   //------------------------------------------------
171   // everything below this line borrowed from Tomcat
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     // Calculate a semi-useful value if this has not been set
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 	  // Calculate the new random number generator seed
256 	  long seed = System.currentTimeMillis();
257 	  //long t1 = seed;
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 	    // Construct and seed a new random number generator
265 	    Class clazz = Class.forName(randomClass);
266 	    this.random = (Random) clazz.newInstance();
267 	    this.random.setSeed(seed);
268 	  } catch (Exception e) {
269 	    // Fall back to the simple case
270             log.error(sm.getString("managerBase.random", randomClass), e);
271 	    this.random = new java.util.Random();
272 	    this.random.setSeed(seed);
273 	  }
274 //	  long t2=System.currentTimeMillis();
275 //	  if( (t2-t1) > 100 )
276 //	    if (log.isTraceEnabled())
277 //	      log.trace(sm.getString("managerBase.seeding", randomClass) + " " + (t2-t1));
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 //	if( log.isTraceEnabled() )
295 //	  log.trace( "Opening " + devRandomSource );
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     // as a hack, you can use a static file - and genarate the same
318     // session ids ( good for strange traceging )
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 //	if( log.isTraceEnabled() )
329 //	  log.trace( "Opening " + devRandomSource );
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     // Render the result as a String of hexadecimal digits
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       //long t1=System.currentTimeMillis();
371 //      if (log.isTraceEnabled())
372 //	log.trace(sm.getString("managerBase.getting", algorithm));
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 //      if (log.isTraceEnabled())
385 //	log.trace(sm.getString("managerBase.gotten"));
386 //      long t2=System.currentTimeMillis();
387 //      if( log.isTraceEnabled() )
388 //	log.trace("getDigest() " + (t2-t1));
389     }
390 
391     return (this.digest);
392 
393   }
394 
395   protected void getRandomBytes( byte bytes[] ) {
396     // Generate a byte array containing a session identifier
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 //	if (log.isTraceEnabled())
407 //	  log.trace("Got " + len + " " + bytes.length );
408       } catch( Exception ex ) {
409       }
410       devRandomSource=null;
411       randomIS=null;
412     }
413 //    Random random = getRandom();
414     getRandom().nextBytes(bytes);
415   }
416 }