blob: 3695e7c3cb9f85c45faf1aac0131e9f2c5078955 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package sun.security.provider;
27
28/**
29 * <P> This class generates seeds for the cryptographically strong random
30 * number generator.
31 * <P> The seed is produced using one of two techniques, via a computation
32 * of current system activity or from an entropy gathering device.
33 * <p> In the default technique the seed is produced by counting the
34 * number of times the VM manages to loop in a given period. This number
35 * roughly reflects the machine load at that point in time.
36 * The samples are translated using a permutation (s-box)
37 * and then XORed together. This process is non linear and
38 * should prevent the samples from "averaging out". The s-box
39 * was designed to have even statistical distribution; it's specific
40 * values are not crucial for the security of the seed.
41 * We also create a number of sleeper threads which add entropy
42 * to the system by keeping the scheduler busy.
43 * Twenty such samples should give us roughly 160 bits of randomness.
44 * <P> These values are gathered in the background by a daemon thread
45 * thus allowing the system to continue performing it's different
46 * activites, which in turn add entropy to the random seed.
47 * <p> The class also gathers miscellaneous system information, some
48 * machine dependent, some not. This information is then hashed together
49 * with the 20 seed bytes.
50 * <P> The alternative to the above approach is to acquire seed material
51 * from an entropy gathering device, such as /dev/random. This can be
52 * accomplished by setting the value of the "securerandom.source"
53 * security property (in the Java security properties file) to a URL
54 * specifying the location of the entropy gathering device.
55 * In the event the specified URL cannot be accessed the default
56 * mechanism is used.
57 * The Java security properties file is located in the file named
58 * &lt;JAVA_HOME&gt;/lib/security/java.security.
59 * &lt;JAVA_HOME&gt; refers to the value of the java.home system property,
60 * and specifies the directory where the JRE is installed.
61 *
62 * @author Joshua Bloch
63 * @author Gadi Guy
64 */
65
66import java.security.*;
67import java.io.*;
68import java.util.Properties;
69import java.util.Enumeration;
70import java.net.*;
71import sun.security.util.Debug;
72
73abstract class SeedGenerator {
74
75 // Static instance is created at link time
76 private static SeedGenerator instance;
77
78 private static final Debug debug = Debug.getInstance("provider");
79
80 final static String URL_DEV_RANDOM = SunEntries.URL_DEV_RANDOM;
81 final static String URL_DEV_URANDOM = SunEntries.URL_DEV_URANDOM;
82
83 // Static initializer to hook in selected or best performing generator
84 static {
85 String egdSource = SunEntries.getSeedSource();
86
87 // Try the URL specifying the source
88 // e.g. file:/dev/random
89 //
90 // The URL file:/dev/random or file:/dev/urandom is used to indicate
91 // the SeedGenerator using OS support, if available.
92 // On Windows, the causes MS CryptoAPI to be used.
93 // On Solaris and Linux, this is the identical to using
94 // URLSeedGenerator to read from /dev/random
95
96 if (egdSource.equals(URL_DEV_RANDOM) || egdSource.equals(URL_DEV_URANDOM)) {
97 try {
98 instance = new NativeSeedGenerator();
99 if (debug != null) {
100 debug.println("Using operating system seed generator");
101 }
102 } catch (IOException e) {
103 if (debug != null) {
104 debug.println("Failed to use operating system seed "
105 + "generator: " + e.toString());
106 }
107 }
108 } else if (egdSource.length() != 0) {
109 try {
110 instance = new URLSeedGenerator(egdSource);
111 if (debug != null) {
112 debug.println("Using URL seed generator reading from "
113 + egdSource);
114 }
115 } catch (IOException e) {
116 if (debug != null)
117 debug.println("Failed to create seed generator with "
118 + egdSource + ": " + e.toString());
119 }
120 }
121
122 // Fall back to ThreadedSeedGenerator
123 if (instance == null) {
124 if (debug != null) {
125 debug.println("Using default threaded seed generator");
126 }
127 instance = new ThreadedSeedGenerator();
128 }
129 }
130
131 /**
132 * Fill result with bytes from the queue. Wait for it if it isn't ready.
133 */
134 static public void generateSeed(byte[] result) {
135 instance.getSeedBytes(result);
136 }
137
138 void getSeedBytes(byte[] result) {
139 for (int i = 0; i < result.length; i++) {
140 result[i] = getSeedByte();
141 }
142 }
143
144 abstract byte getSeedByte();
145
146 /**
147 * Retrieve some system information, hashed.
148 */
149 static byte[] getSystemEntropy() {
150 byte[] ba;
151 final MessageDigest md;
152
153 try {
154 md = MessageDigest.getInstance("SHA");
155 } catch (NoSuchAlgorithmException nsae) {
156 throw new InternalError("internal error: SHA-1 not available.");
157 }
158
159 // The current time in millis
160 byte b =(byte)System.currentTimeMillis();
161 md.update(b);
162
163 java.security.AccessController.doPrivileged
164 (new java.security.PrivilegedAction<Void>() {
165 public Void run() {
166
167 try {
168 // System properties can change from machine to machine
169 String s;
170 Properties p = System.getProperties();
171 Enumeration<?> e = p.propertyNames();
172 while (e.hasMoreElements()) {
173 s =(String)e.nextElement();
174 md.update(s.getBytes());
175 md.update(p.getProperty(s).getBytes());
176 }
177
178 md.update
179 (InetAddress.getLocalHost().toString().getBytes());
180
181 // The temporary dir
182 File f = new File(p.getProperty("java.io.tmpdir"));
183 String[] sa = f.list();
184 for(int i = 0; i < sa.length; i++)
185 md.update(sa[i].getBytes());
186
187 } catch (Exception ex) {
188 md.update((byte)ex.hashCode());
189 }
190
191 // get Runtime memory stats
192 Runtime rt = Runtime.getRuntime();
193 byte[] memBytes = longToByteArray(rt.totalMemory());
194 md.update(memBytes, 0, memBytes.length);
195 memBytes = longToByteArray(rt.freeMemory());
196 md.update(memBytes, 0, memBytes.length);
197
198 return null;
199 }
200 });
201 return md.digest();
202 }
203
204 /**
205 * Helper function to convert a long into a byte array (least significant
206 * byte first).
207 */
208 private static byte[] longToByteArray(long l) {
209 byte[] retVal = new byte[8];
210
211 for (int i=0; i<8; i++) {
212 retVal[i] = (byte) l;
213 l >>= 8;
214 }
215
216 return retVal;
217 }
218
219 /*
220 // This method helps the test utility receive unprocessed seed bytes.
221 public static int genTestSeed() {
222 return myself.getByte();
223 }
224 */
225
226
227 private static class ThreadedSeedGenerator extends SeedGenerator implements Runnable {
228 // Queue is used to collect seed bytes
229 private byte[] pool;
230 private int start, end, count;
231
232 // Thread group for our threads
233 ThreadGroup seedGroup;
234
235 /**
236 * The constructor is only called once to construct the one
237 * instance we actually use. It instantiates the message digest
238 * and starts the thread going.
239 */
240
241 ThreadedSeedGenerator() {
242 pool = new byte[20];
243 start = end = 0;
244
245 MessageDigest digest;
246
247 try {
248 digest = MessageDigest.getInstance("SHA");
249 } catch (NoSuchAlgorithmException e) {
250 throw new InternalError("internal error: SHA-1 not available.");
251 }
252
253 final ThreadGroup[] finalsg = new ThreadGroup[1];
254 Thread t = java.security.AccessController.doPrivileged
255 (new java.security.PrivilegedAction<Thread>() {
256 public Thread run() {
257 ThreadGroup parent, group =
258 Thread.currentThread().getThreadGroup();
259 while ((parent = group.getParent()) != null)
260 group = parent;
261 finalsg[0] = new ThreadGroup
262 (group, "SeedGenerator ThreadGroup");
263 Thread newT = new Thread(finalsg[0],
264 ThreadedSeedGenerator.this,
265 "SeedGenerator Thread");
266 newT.setPriority(Thread.MIN_PRIORITY);
267 newT.setDaemon(true);
268 return newT;
269 }
270 });
271 seedGroup = finalsg[0];
272 t.start();
273 }
274
275 /**
276 * This method does the actual work. It collects random bytes and
277 * pushes them into the queue.
278 */
279 final public void run() {
280 try {
281 while (true) {
282 // Queue full? Wait till there's room.
283 synchronized(this) {
284 while (count >= pool.length)
285 wait();
286 }
287
288 int counter, quanta;
289 byte v = 0;
290
291 // Spin count must not be under 64000
292 for (counter = quanta = 0; (counter < 64000) && (quanta < 6);
293 quanta++) {
294
295 // Start some noisy threads
296 try {
297 BogusThread bt = new BogusThread();
298 Thread t = new Thread
299 (seedGroup, bt, "SeedGenerator Thread");
300 t.start();
301 } catch (Exception e) {
302 throw new InternalError("internal error: " +
303 "SeedGenerator thread creation error.");
304 }
305
306 // We wait 250milli quanta, so the minimum wait time
307 // cannot be under 250milli.
308 int latch = 0;
309 latch = 0;
310 long l = System.currentTimeMillis() + 250;
311 while (System.currentTimeMillis() < l) {
312 synchronized(this){};
313 latch++;
314 }
315
316 // Translate the value using the permutation, and xor
317 // it with previous values gathered.
318 v ^= rndTab[latch % 255];
319 counter += latch;
320 }
321
322 // Push it into the queue and notify anybody who might
323 // be waiting for it.
324 synchronized(this) {
325 pool[end] = v;
326 end++;
327 count++;
328 if (end >= pool.length)
329 end = 0;
330
331 notifyAll();
332 }
333 }
334 } catch (Exception e) {
335 throw new InternalError("internal error: " +
336 "SeedGenerator thread generated an exception.");
337 }
338 }
339
340 byte getSeedByte() {
341 byte b = 0;
342
343 try {
344 // Wait for it...
345 synchronized(this) {
346 while (count <= 0)
347 wait();
348 }
349 } catch (Exception e) {
350 if (count <= 0)
351 throw new InternalError("internal error: " +
352 "SeedGenerator thread generated an exception.");
353 }
354
355 synchronized(this) {
356 // Get it from the queue
357 b = pool[start];
358 pool[start] = 0;
359 start++;
360 count--;
361 if (start == pool.length)
362 start = 0;
363
364 // Notify the daemon thread, just in case it is
365 // waiting for us to make room in the queue.
366 notifyAll();
367 }
368
369 return b;
370 }
371
372 // The permutation was calculated by generating 64k of random
373 // data and using it to mix the trivial permutation.
374 // It should be evenly distributed. The specific values
375 // are not crucial to the security of this class.
376 private static byte[] rndTab = {
377 56, 30, -107, -6, -86, 25, -83, 75, -12, -64,
378 5, -128, 78, 21, 16, 32, 70, -81, 37, -51,
379 -43, -46, -108, 87, 29, 17, -55, 22, -11, -111,
380 -115, 84, -100, 108, -45, -15, -98, 72, -33, -28,
381 31, -52, -37, -117, -97, -27, 93, -123, 47, 126,
382 -80, -62, -93, -79, 61, -96, -65, -5, -47, -119,
383 14, 89, 81, -118, -88, 20, 67, -126, -113, 60,
384 -102, 55, 110, 28, 85, 121, 122, -58, 2, 45,
385 43, 24, -9, 103, -13, 102, -68, -54, -101, -104,
386 19, 13, -39, -26, -103, 62, 77, 51, 44, 111,
387 73, 18, -127, -82, 4, -30, 11, -99, -74, 40,
388 -89, 42, -76, -77, -94, -35, -69, 35, 120, 76,
389 33, -73, -7, 82, -25, -10, 88, 125, -112, 58,
390 83, 95, 6, 10, 98, -34, 80, 15, -91, 86,
391 -19, 52, -17, 117, 49, -63, 118, -90, 36, -116,
392 -40, -71, 97, -53, -109, -85, 109, -16, -3, 104,
393 -95, 68, 54, 34, 26, 114, -1, 106, -121, 3,
394 66, 0, 100, -84, 57, 107, 119, -42, 112, -61,
395 1, 48, 38, 12, -56, -57, 39, -106, -72, 41,
396 7, 71, -29, -59, -8, -38, 79, -31, 124, -124,
397 8, 91, 116, 99, -4, 9, -36, -78, 63, -49,
398 -67, -87, 59, 101, -32, 92, 94, 53, -41, 115,
399 -66, -70, -122, 50, -50, -22, -20, -18, -21, 23,
400 -2, -48, 96, 65, -105, 123, -14, -110, 69, -24,
401 -120, -75, 74, 127, -60, 113, 90, -114, 105, 46,
402 27, -125, -23, -44, 64
403 };
404
405 /**
406 * This inner thread causes the thread scheduler to become 'noisy',
407 * thus adding entropy to the system load.
408 * At least one instance of this class is generated for every seed byte.
409 */
410
411 private static class BogusThread implements Runnable {
412 final public void run() {
413 try {
414 for(int i = 0; i < 5; i++)
415 Thread.sleep(50);
416 // System.gc();
417 } catch (Exception e) {
418 }
419 }
420 }
421 }
422
423 static class URLSeedGenerator extends SeedGenerator {
424
425 private String deviceName;
426 private BufferedInputStream devRandom;
427
428
429 /**
430 * The constructor is only called once to construct the one
431 * instance we actually use. It opens the entropy gathering device
432 * which will supply the randomness.
433 */
434
435 URLSeedGenerator(String egdurl) throws IOException {
436 if (egdurl == null) {
437 throw new IOException("No random source specified");
438 }
439 deviceName = egdurl;
440 init();
441 }
442
443 URLSeedGenerator() throws IOException {
444 this(SeedGenerator.URL_DEV_RANDOM);
445 }
446
447 private void init() throws IOException {
448 final URL device = new URL(deviceName);
449 devRandom = java.security.AccessController.doPrivileged
450 (new java.security.PrivilegedAction<BufferedInputStream>() {
451 public BufferedInputStream run() {
452 try {
453 return new BufferedInputStream(device.openStream());
454 } catch (IOException ioe) {
455 return null;
456 }
457 }
458 });
459
460 if (devRandom == null) {
461 throw new IOException("failed to open " + device);
462 }
463 }
464
465 byte getSeedByte() {
466 byte b[] = new byte[1];
467 int stat;
468 try {
469 stat = devRandom.read(b, 0, b.length);
470 } catch (IOException ioe) {
471 throw new InternalError("URLSeedGenerator " + deviceName +
472 " generated exception: " +
473 ioe.getMessage());
474 }
475 if (stat == b.length) {
476 return b[0];
477 } else if (stat == -1) {
478 throw new InternalError("URLSeedGenerator " + deviceName +
479 " reached end of file");
480 } else {
481 throw new InternalError("URLSeedGenerator " + deviceName +
482 " failed read");
483 }
484 }
485
486 }
487
488}