/*
* Copyright (c) 2003, 2008, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 in the LICENSE file that
* accompanied this code).
*
* 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.provider;
import java.io.*;
import java.security.*;
import java.security.SecureRandom;
Native PRNG implementation for Solaris/Linux. It interacts with
/dev/random and /dev/urandom, so it is only available if those
files are present. Otherwise, SHA1PRNG is used instead of this class.
getSeed() and setSeed() directly read/write /dev/random. However,
/dev/random is only writable by root in many configurations. Because
we cannot just ignore bytes specified via setSeed(), we keep a
SHA1PRNG around in parallel.
nextBytes() reads the bytes directly from /dev/urandom (and then
mixes them with bytes from the SHA1PRNG for the reasons explained
above). Reading bytes from /dev/urandom means that constantly get
new entropy the operating system has collected. This is a notable
advantage over the SHA1PRNG model, which acquires entropy only
initially during startup although the VM may be running for months.
Also note that we do not need any initial pure random seed from
/dev/random. This is an advantage because on some versions of Linux
it can be exhausted very quickly and could thus impact startup time.
Finally, note that we use a singleton for the actual work (RandomIO)
to avoid having to open and close /dev/[u]random constantly. However,
there may me many NativePRNG instances created by the JCA framework.
Author: Andreas Sterbenz Since: 1.5
/**
* Native PRNG implementation for Solaris/Linux. It interacts with
* /dev/random and /dev/urandom, so it is only available if those
* files are present. Otherwise, SHA1PRNG is used instead of this class.
*
* getSeed() and setSeed() directly read/write /dev/random. However,
* /dev/random is only writable by root in many configurations. Because
* we cannot just ignore bytes specified via setSeed(), we keep a
* SHA1PRNG around in parallel.
*
* nextBytes() reads the bytes directly from /dev/urandom (and then
* mixes them with bytes from the SHA1PRNG for the reasons explained
* above). Reading bytes from /dev/urandom means that constantly get
* new entropy the operating system has collected. This is a notable
* advantage over the SHA1PRNG model, which acquires entropy only
* initially during startup although the VM may be running for months.
*
* Also note that we do not need any initial pure random seed from
* /dev/random. This is an advantage because on some versions of Linux
* it can be exhausted very quickly and could thus impact startup time.
*
* Finally, note that we use a singleton for the actual work (RandomIO)
* to avoid having to open and close /dev/[u]random constantly. However,
* there may me many NativePRNG instances created by the JCA framework.
*
* @since 1.5
* @author Andreas Sterbenz
*/
public final class NativePRNG extends SecureRandomSpi {
private static final long serialVersionUID = -6599091113397072932L;
// name of the pure random file (also used for setSeed())
private static final String NAME_RANDOM = "/dev/random";
// name of the pseudo random file
private static final String NAME_URANDOM = "/dev/urandom";
// singleton instance or null if not available
private static final RandomIO INSTANCE = initIO();
private static RandomIO initIO() {
return AccessController.doPrivileged(
new PrivilegedAction<RandomIO>() {
public RandomIO run() {
File randomFile = new File(NAME_RANDOM);
if (randomFile.exists() == false) {
return null;
}
File urandomFile = new File(NAME_URANDOM);
if (urandomFile.exists() == false) {
return null;
}
try {
return new RandomIO(randomFile, urandomFile);
} catch (Exception e) {
return null;
}
}
});
}
// return whether the NativePRNG is available
static boolean isAvailable() {
return INSTANCE != null;
}
// constructor, called by the JCA framework
public NativePRNG() {
super();
if (INSTANCE == null) {
throw new AssertionError("NativePRNG not available");
}
}
// set the seed
protected void engineSetSeed(byte[] seed) {
INSTANCE.implSetSeed(seed);
}
// get pseudo random bytes
protected void engineNextBytes(byte[] bytes) {
INSTANCE.implNextBytes(bytes);
}
// get true random bytes
protected byte[] engineGenerateSeed(int numBytes) {
return INSTANCE.implGenerateSeed(numBytes);
}
Nested class doing the actual work. Singleton, see INSTANCE above.
/**
* Nested class doing the actual work. Singleton, see INSTANCE above.
*/
private static class RandomIO {
// we buffer data we read from /dev/urandom for efficiency,
// but we limit the lifetime to avoid using stale bits
// lifetime in ms, currently 100 ms (0.1 s)
private final static long MAX_BUFFER_TIME = 100;
// size of the /dev/urandom buffer
private final static int BUFFER_SIZE = 32;
// In/OutputStream for /dev/random and /dev/urandom
private final InputStream randomIn, urandomIn;
private OutputStream randomOut;
// flag indicating if we have tried to open randomOut yet
private boolean randomOutInitialized;
// SHA1PRNG instance for mixing
// initialized lazily on demand to avoid problems during startup
private volatile sun.security.provider.SecureRandom mixRandom;
// buffer for /dev/urandom bits
private final byte[] urandomBuffer;
// number of bytes left in urandomBuffer
private int buffered;
// time we read the data into the urandomBuffer
private long lastRead;
// mutex lock for nextBytes()
private final Object LOCK_GET_BYTES = new Object();
// mutex lock for getSeed()
private final Object LOCK_GET_SEED = new Object();
// mutex lock for setSeed()
private final Object LOCK_SET_SEED = new Object();
// constructor, called only once from initIO()
private RandomIO(File randomFile, File urandomFile) throws IOException {
randomIn = new FileInputStream(randomFile);
urandomIn = new FileInputStream(urandomFile);
urandomBuffer = new byte[BUFFER_SIZE];
}
// get the SHA1PRNG for mixing
// initialize if not yet created
private sun.security.provider.SecureRandom getMixRandom() {
sun.security.provider.SecureRandom r = mixRandom;
if (r == null) {
synchronized (LOCK_GET_BYTES) {
r = mixRandom;
if (r == null) {
r = new sun.security.provider.SecureRandom();
try {
byte[] b = new byte[20];
readFully(urandomIn, b);
r.engineSetSeed(b);
} catch (IOException e) {
throw new ProviderException("init failed", e);
}
mixRandom = r;
}
}
}
return r;
}
// read data.length bytes from in
// /dev/[u]random are not normal files, so we need to loop the read.
// just keep trying as long as we are making progress
private static void readFully(InputStream in, byte[] data)
throws IOException {
int len = data.length;
int ofs = 0;
while (len > 0) {
int k = in.read(data, ofs, len);
if (k <= 0) {
throw new EOFException("/dev/[u]random closed?");
}
ofs += k;
len -= k;
}
if (len > 0) {
throw new IOException("Could not read from /dev/[u]random");
}
}
// get true random bytes, just read from /dev/random
private byte[] implGenerateSeed(int numBytes) {
synchronized (LOCK_GET_SEED) {
try {
byte[] b = new byte[numBytes];
readFully(randomIn, b);
return b;
} catch (IOException e) {
throw new ProviderException("generateSeed() failed", e);
}
}
}
// supply random bytes to the OS
// write to /dev/random if possible
// always add the seed to our mixing random
private void implSetSeed(byte[] seed) {
synchronized (LOCK_SET_SEED) {
if (randomOutInitialized == false) {
randomOutInitialized = true;
randomOut = AccessController.doPrivileged(
new PrivilegedAction<OutputStream>() {
public OutputStream run() {
try {
return new FileOutputStream(NAME_RANDOM, true);
} catch (Exception e) {
return null;
}
}
});
}
if (randomOut != null) {
try {
randomOut.write(seed);
} catch (IOException e) {
throw new ProviderException("setSeed() failed", e);
}
}
getMixRandom().engineSetSeed(seed);
}
}
// ensure that there is at least one valid byte in the buffer
// if not, read new bytes
private void ensureBufferValid() throws IOException {
long time = System.currentTimeMillis();
if ((buffered > 0) && (time - lastRead < MAX_BUFFER_TIME)) {
return;
}
lastRead = time;
readFully(urandomIn, urandomBuffer);
buffered = urandomBuffer.length;
}
// get pseudo random bytes
// read from /dev/urandom and XOR with bytes generated by the
// mixing SHA1PRNG
private void implNextBytes(byte[] data) {
synchronized (LOCK_GET_BYTES) {
try {
getMixRandom().engineNextBytes(data);
int len = data.length;
int ofs = 0;
while (len > 0) {
ensureBufferValid();
int bufferOfs = urandomBuffer.length - buffered;
while ((len > 0) && (buffered > 0)) {
data[ofs++] ^= urandomBuffer[bufferOfs++];
len--;
buffered--;
}
}
} catch (IOException e) {
throw new ProviderException("nextBytes() failed", e);
}
}
}
}
}