001: /*
002: * Enhydra Java Application Server Project
003: *
004: * The contents of this file are subject to the Enhydra Public License
005: * Version 1.1 (the "License"); you may not use this file except in
006: * compliance with the License. You may obtain a copy of the License on
007: * the Enhydra web site ( http://www.enhydra.org/ ).
008: *
009: * Software distributed under the License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
011: * the License for the specific terms governing rights and limitations
012: * under the License.
013: *
014: * The Initial Developer of the Enhydra Application Server is Lutris
015: * Technologies, Inc. The Enhydra Application Server and portions created
016: * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
017: * All Rights Reserved.
018: *
019: * Contributor(s):
020: *
021: * $Id: StandardSessionKeyGen.java,v 1.2 2006-06-15 13:44:07 sinisa Exp $
022: */
023:
024: package com.lutris.appserver.server.sessionEnhydra;
025:
026: import java.io.ByteArrayOutputStream;
027: import java.io.DataOutputStream;
028: import java.io.IOException;
029: import java.security.MessageDigest;
030: import java.security.NoSuchAlgorithmException;
031: import java.security.SecureRandom;
032:
033: import com.lutris.util.Base64Encoder;
034: import com.lutris.util.FatalExceptionError;
035:
036: /**
037: * The session random key generator. This class implements a background thread
038: * that wakes up and counts the number of Standard Session Manager requests
039: * completed at one or more different interval periods, and
040: * supplements the seed of the Manager's random number generator
041: * in order to make the value of the cookies extremely unpredictable.
042: * This is an absolute requirement if random cookie values are to
043: * be used for any type of security purpose.
044: *
045: * This random number generator uses the JDK 1.1 <code>SecureRandom</code>
046: * object, which implements a cryptographic grade random number
047: * generator based on the RSA MD5 one-way hash. In combination with
048: * external user-generated time delay information, the numbers
049: * generated by this object are highly unpredictable, and therefore
050: * suitably secure for their use as session keys.
051: *
052: * @version $Revision: 1.2 $
053: * @author John Marco
054: * @author Shawn McMurdo
055: */
056: public class StandardSessionKeyGen extends Thread {
057:
058: /**
059: * Random number generator used to generate session keys.
060: *
061: * @see com.lutris.StandardSessionManager#randomThread
062: * @see java.security.SecureRandom
063: */
064: private SecureRandom randomizer = new SecureRandom();
065:
066: // If you want to improve startup performance significantly
067: // use the following constructor.
068: // new SecureRandom(new Long(System.currentTimeMillis()).toString().getBytes());
069:
070: /**
071: * Counter that is incremented by the user of this class to provide a
072: * random external value. Usually done on requests.
073: */
074: private int randomCounter = 0;
075:
076: /*
077: * Timer intervals.
078: */
079: private long[] alarmVector;
080: private long[] intervalVector;
081: private int numIntervals;
082:
083: /**
084: * Constructor a new key generator random number entropy
085: * generator. Initializes timers and counters and start a
086: * thread.
087: *
088: * @param manager The Standard session manager to be randomized.
089: * @param intervals An array of one or more intervals, in seconds
090: * in which to periodically supplement the random number
091: * generator with external user-generated entropy.
092: */
093: public StandardSessionKeyGen(long intervals[]) {
094: int i;
095: long now = System.currentTimeMillis();
096: numIntervals = intervals.length;
097: intervalVector = new long[numIntervals];
098:
099: //Convert each interval value from seconds to milliseconds.
100: for (i = 0; i < numIntervals; i++) {
101: intervalVector[i] = intervals[i] * 1000; // Milliseconds.
102: }
103: alarmVector = new long[numIntervals];
104: for (i = 0; i < numIntervals; i++) {
105: alarmVector[i] = now + intervalVector[i];
106: }
107: setDaemon(true); // Don't require this thread to exit.
108: }
109:
110: /**
111: * The main code body of the Idle Timer Thread. Enters an endless
112: * loop that sleeps for a configurable period, periodically waking
113: * up to modify the session manager's random seed. An externally
114: * incremented count is used as a source of user-generated randomness.
115: */
116: public void run() {
117: ByteArrayOutputStream bos = new ByteArrayOutputStream();
118: DataOutputStream dos = new DataOutputStream(bos);
119: MessageDigest md5 = null;
120: int i;
121: long now, least, interval;
122:
123: try {
124: md5 = MessageDigest.getInstance("MD5");
125: } catch (NoSuchAlgorithmException e) {
126: // JDK1.1 always has MD5.
127: throw new FatalExceptionError(e);
128: }
129: while (true) {
130: now = System.currentTimeMillis();
131: least = Long.MAX_VALUE;
132:
133: // Check each alarm for expiry, and processes if needed.
134: for (i = 0; i < numIntervals; i++) {
135: if (now >= alarmVector[i]) {
136: try {
137: dos.writeLong(randomCounter);
138: dos.writeLong(now);
139: dos.flush();
140: } catch (IOException e) {
141: throw new FatalExceptionError(e); //- won't happen.
142: }
143: randomizer.setSeed(md5.digest(bos.toByteArray()));
144:
145: // Set the alarm to its next wakeup time.
146: alarmVector[i] = now + intervalVector[i];
147: }
148:
149: // Find the next scheduled alarm.
150: if (alarmVector[i] < least) {
151: least = alarmVector[i];
152: }
153: }
154:
155: // Wait for the next scheduled alarm.
156: interval = least - now;
157: if (interval <= 0) {
158: interval = 1;
159: }
160: try {
161: sleep(interval);
162: } catch (InterruptedException e) {
163: // Don't throw interrupts.
164: }
165: }
166: }
167:
168: /**
169: * Increment the random counter. Used for randomization, so doesn't
170: * have to be completely accurate and is not sychronized. The normal
171: * way to use this is to increment it on requests generated by external
172: * sources.
173: */
174: public void incrementRandomCounter() {
175: randomCounter++;
176: }
177:
178: /**
179: * Generates a new random key to identify a session.
180: * This key represents a random integer that is large and sparse
181: * enough to make it highly unlikely that a valid session key can
182: * be guessed by an intruder. The <code>randomizer</code> object is
183: * used to generate this key.<P>
184: *
185: * This function is reentrant and does not need synchronization.
186: *
187: * @return A string representing a random key. The characters
188: * in this key are constrained to [A-Za-z0-9_-]. The
189: * encoding is more or less Base 64, but instead of '+'
190: * and '/' as defined in RFC1521, the characters '_' and
191: * '-' are used because they are safe in URLs and file names.
192: */
193: public String newSessionKey() {
194: try {
195: byte[] rand32 = new byte[32];
196: byte[] rand2 = new byte[2];
197: byte[] md5result;
198: long now = System.currentTimeMillis();
199: ByteArrayOutputStream bos = new ByteArrayOutputStream();
200: DataOutputStream dos = new DataOutputStream(bos);
201: MessageDigest md5 = MessageDigest.getInstance("MD5");
202: randomizer.nextBytes(rand32);
203: randomizer.nextBytes(rand2);
204: dos.write(rand32, 0, rand32.length);
205: dos.writeLong(now);
206: dos.flush();
207: md5result = md5.digest(bos.toByteArray());
208: bos.reset();
209: dos = new DataOutputStream(bos);
210: dos.write(md5result, 0, md5result.length);
211: dos.write(rand2, 0, rand2.length);
212: return Base64Encoder.toBase64SessionKeyString(bos
213: .toByteArray());
214: } catch (IOException e) {
215: // Byte stream; should never happen.
216: throw new FatalExceptionError(e);
217: } catch (NoSuchAlgorithmException e) {
218: // JDK 1.1 has MD5m, should never happen.
219: throw new FatalExceptionError(e);
220: }
221: }
222:
223: /**
224: * Shutdown the thread associated with this object.
225: */
226: public void shutdown() {
227: stop();
228: }
229: }
|