/*
* @(#)NativeSeedGenerator.java 1.5 06/10/10
*
* Copyright 1990-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package sun.security.provider;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.net.URL;
import sun.security.util.Debug;
/**
* Native seed generator for Linix systems. Inherit everything from
* URLSeedGenerator.
*
* @version 1.2, 01/23/03
*/
class NativeSeedGenerator extends SeedGenerator.URLSeedGenerator {
private static final Debug debug = Debug.getInstance("provider");
private String deviceName;
private BufferedInputStream devRandom;
// For CDC/FP 1.1.1 we provide a fallback mechanism to deal with
// the fact that on some devices the entropy gathering device
// can block. The ThreadedSeedGenerator approach is not an ideal
// solution for the range of devices which CDC/FP addresses, so
// we will use /dev/urandom, which will not block.
final static String URL_FALLBACK = "file:/dev/urandom";
// Allow the fallback mechanism to be disabled if desired. This
// could mean that an application's main threat will hang,
// but it allows for stronger random seed generation.
private final static String PROP_NOFALLBACK =
"microedition.securerandom.nofallback";
private static boolean createFallback = true;
// The actual fallback InputStream from which we read.
private BufferedInputStream fallback;
// An object to sit between the main thread and the
// entropy generation device, to avoid blocking an
// application if there is not sufficient entropy.
private static RandomReader ranReader = null;
NativeSeedGenerator() throws IOException {
this(SeedGenerator.URL_DEV_RANDOM);
}
NativeSeedGenerator(String egdurl) throws IOException {
if (egdurl == null) {
throw new IOException("No random source specified");
}
this.deviceName = egdurl;
// Because of the problems of blocking I/O on smaller
// devices, we do not read the random device directly,
// but rather do so in a separate thread from which we
// can retrieve data when it is available.
if ( ranReader == null ) {
ranReader = new RandomReader( egdurl );
if (ranReader == null) {
throw new InternalError( "RandomReader thread creation failure" );
}
}
// Check to see whether the fallback mechanism is disabled.
Boolean noFallback =
(Boolean)java.security.AccessController.doPrivileged
(new java.security.PrivilegedAction() {
public Object run() {
String prop = System.getProperty(PROP_NOFALLBACK,
"false");
return Boolean.valueOf(prop);
}
});
createFallback = ! noFallback.booleanValue();
}
// For CDC/FP, if the fallback mechanism is not disabled
// and we are going to block on the default entropy generation
// device, we'll open the fallback URL instead.
private void createFallback() throws IOException {
if (debug != null) {
debug.println("Creating fallback seed generator");
}
final URL fallbackDevice = new URL(this.URL_FALLBACK);
fallback =
(BufferedInputStream)java.security.AccessController.doPrivileged
(new java.security.PrivilegedAction() {
public Object run() {
try {
return new BufferedInputStream(fallbackDevice.openStream());
} catch ( IOException ioe ) {
return null;
}
}
});
if (fallback == null) {
throw new IOException( "failed to open " +
this.URL_FALLBACK );
}
return;
}
synchronized byte getSeedByte() {
byte b[] = new byte[1];
int stat;
// If we would block trying to read from the entropy
// generation device, and we haven't been instructed
// not to, use a fallback alternative which will not
// block.
if (ranReader.available() == 0 && createFallback == true) {
try {
if (fallback == null) {
createFallback();
}
// Do the actual read. If we get a byte of
// data, give it to whomever wanted it.
stat = fallback.read(b, 0, b.length);
if (stat == b.length) {
return b[0];
}
} catch (IOException ioe) {
throw new InternalError("NativeSeedGenerator " +
this.URL_FALLBACK +
" generated exception: " +
ioe.getMessage());
} catch (Exception e) {
throw new InternalError("NativeSeedGenerator " +
this.URL_FALLBACK +
" generated exception: " +
e.getMessage());
}
// The read didn't generate an exception but gave us
// bad data in any case.
throw new InternalError("NativeSeedGenerator " +
this.URL_FALLBACK +
" failed read: " + stat );
}
// Read from the main entropy device. This happens if data is
// available *or* if the property has been set which
// instructs us not to use the fallback device no matter
// what.
return ranReader.getSeedByte();
}
/*
* CR 6260366. Because of varying methods for generating
* entropy in the default random device (/dev/random, generally),
* we will read from it in a separate thread. This will allow
* reads to block until the device has generated sufficient
* entropy. If the device will block, we will instead default
* to file:/dev/urandom.
*/
private static class RandomReader implements Runnable {
private String deviceName;
private BufferedInputStream randomDevReader;
private Thread readerThread;
byte[] buf = new byte[ 20 ];
int start = 0;
int end = 0;
RandomReader(String egdurl) throws IOException {
if (egdurl == null) {
throw new IOException("No random source specified");
}
deviceName = egdurl;
init();
}
// Initialize ourselves. Open the specified device and
// start the run() method.
void init() throws IOException {
final URL device = new URL(deviceName);
randomDevReader =
(BufferedInputStream)java.security.AccessController.doPrivileged
(new java.security.PrivilegedAction() {
public Object run() {
try {
return new BufferedInputStream(device.openStream(), 1);
} catch (IOException ioe) {
return null;
}
}
});
// Oops. Check for failure.
if (randomDevReader == null) {
throw new IOException("failed to open " + device);
}
// Create a thread which will do the actual reading.
readerThread = (Thread)java.security.AccessController.doPrivileged
(new java.security.PrivilegedAction() {
public Object run() {
Thread newT = new Thread(Thread.currentThread().getThreadGroup(),
RandomReader.this,
"Random Device Reader Thread");
newT.setPriority(Thread.MIN_PRIORITY);
newT.setDaemon(true);
return newT;
}
});
if (readerThread == null) {
throw new InternalError("Cannot create RandomReader thread for " + deviceName);
}
readerThread.start();
return;
}
// The real work of RandomReader is done here. Read from the
// random device (which may block in some implementations).
// Retrieved bytes will be taken from our buffer by the
// getSeedByte() method.
public void run() {
while (true) {
try {
// If we don't need to read any data, wait until
// the buffer is empty.
synchronized(this) {
while (start < end) {
wait();
}
}
// Read to fill the buffer
end = randomDevReader.read( buf, 0, buf.length );
if (end < 0) {
throw new InternalError("RandomReader read failed on " + deviceName);
}
start = 0;
// Notify anyone looking for data that the
// buffer is full.
synchronized(this) {
notify();
}
} catch (Exception e) {
throw new InternalError("RandomReader thread generated exception: " +
e.getMessage());
}
}
}
// Is there anything available in the buffer to consume?
int available() {
return end - start;
}
// Take a byte from the buffer.
byte getSeedByte() {
byte b = 0;
try {
synchronized(this) {
// If we have no data, wait until it becomes
// available. The buffer will be populated by
// the run() method.
if (start == end) {
wait();
}
b = buf[start++];
notifyAll();
}
} catch (Exception e) {
throw new InternalError("RandomReader thread failed getSeedByte");
}
return b;
}
}
}