|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.provider; |
|
|
|
/** |
|
* This class generates seeds for the SHA1PRNG cryptographically strong |
|
* random number generator. |
|
* <p> |
|
* The seed is produced using one of two techniques, via a computation |
|
* of current system activity or from an entropy gathering device. |
|
* <p> |
|
* In the default technique the seed is produced by counting the |
|
* number of times the VM manages to loop in a given period. This number |
|
* roughly reflects the machine load at that point in time. |
|
* The samples are translated using a permutation (s-box) |
|
* and then XORed together. This process is non linear and |
|
* should prevent the samples from "averaging out". The s-box |
|
* was designed to have even statistical distribution; it's specific |
|
* values are not crucial for the security of the seed. |
|
* We also create a number of sleeper threads which add entropy |
|
* to the system by keeping the scheduler busy. |
|
* Twenty such samples should give us roughly 160 bits of randomness. |
|
* <p> |
|
* These values are gathered in the background by a daemon thread |
|
* thus allowing the system to continue performing it's different |
|
* activites, which in turn add entropy to the random seed. |
|
* <p> |
|
* The class also gathers miscellaneous system information, some |
|
* machine dependent, some not. This information is then hashed together |
|
* with the 20 seed bytes. |
|
* <p> |
|
* The alternative to the above approach is to acquire seed material |
|
* from an entropy gathering device, such as /dev/random. This can be |
|
* accomplished by setting the value of the {@code securerandom.source} |
|
* Security property to a URL specifying the location of the entropy |
|
* gathering device, or by setting the {@code java.security.egd} System |
|
* property. |
|
* <p> |
|
* In the event the specified URL cannot be accessed the default |
|
* threading mechanism is used. |
|
* |
|
* @author Joshua Bloch |
|
* @author Gadi Guy |
|
*/ |
|
|
|
import java.security.*; |
|
import java.io.*; |
|
import java.util.Properties; |
|
import java.util.Enumeration; |
|
import java.net.*; |
|
import java.nio.file.DirectoryStream; |
|
import java.nio.file.Files; |
|
import java.nio.file.Path; |
|
import java.util.Random; |
|
import sun.security.util.Debug; |
|
|
|
abstract class SeedGenerator { |
|
|
|
|
|
private static SeedGenerator instance; |
|
|
|
private static final Debug debug = Debug.getInstance("provider"); |
|
|
|
|
|
static { |
|
String egdSource = SunEntries.getSeedSource(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
if (egdSource.equals(SunEntries.URL_DEV_RANDOM) || |
|
egdSource.equals(SunEntries.URL_DEV_URANDOM)) { |
|
try { |
|
instance = new NativeSeedGenerator(egdSource); |
|
if (debug != null) { |
|
debug.println( |
|
"Using operating system seed generator" + egdSource); |
|
} |
|
} catch (IOException e) { |
|
if (debug != null) { |
|
debug.println("Failed to use operating system seed " |
|
+ "generator: " + e.toString()); |
|
} |
|
} |
|
} else if (egdSource.length() != 0) { |
|
try { |
|
instance = new URLSeedGenerator(egdSource); |
|
if (debug != null) { |
|
debug.println("Using URL seed generator reading from " |
|
+ egdSource); |
|
} |
|
} catch (IOException e) { |
|
if (debug != null) { |
|
debug.println("Failed to create seed generator with " |
|
+ egdSource + ": " + e.toString()); |
|
} |
|
} |
|
} |
|
|
|
|
|
if (instance == null) { |
|
if (debug != null) { |
|
debug.println("Using default threaded seed generator"); |
|
} |
|
instance = new ThreadedSeedGenerator(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
public static void generateSeed(byte[] result) { |
|
instance.getSeedBytes(result); |
|
} |
|
|
|
abstract void getSeedBytes(byte[] result); |
|
|
|
|
|
|
|
*/ |
|
static byte[] getSystemEntropy() { |
|
final MessageDigest md; |
|
|
|
try { |
|
md = MessageDigest.getInstance("SHA"); |
|
} catch (NoSuchAlgorithmException nsae) { |
|
throw new InternalError("internal error: SHA-1 not available.", |
|
nsae); |
|
} |
|
|
|
|
|
byte b =(byte)System.currentTimeMillis(); |
|
md.update(b); |
|
|
|
java.security.AccessController.doPrivileged |
|
(new java.security.PrivilegedAction<>() { |
|
@Override |
|
public Void run() { |
|
try { |
|
|
|
Properties p = System.getProperties(); |
|
for (String s: p.stringPropertyNames()) { |
|
md.update(s.getBytes()); |
|
md.update(p.getProperty(s).getBytes()); |
|
} |
|
|
|
|
|
addNetworkAdapterInfo(md); |
|
|
|
|
|
File f = new File(p.getProperty("java.io.tmpdir")); |
|
int count = 0; |
|
try ( |
|
DirectoryStream<Path> stream = |
|
Files.newDirectoryStream(f.toPath())) { |
|
// We use a Random object to choose what file names |
|
// should be used. Otherwise on a machine with too |
|
// many files, the same first 1024 files always get |
|
// used. Any, We make sure the first 512 files are |
|
|
|
Random r = new Random(); |
|
for (Path entry: stream) { |
|
if (count < 512 || r.nextBoolean()) { |
|
md.update(entry.getFileName() |
|
.toString().getBytes()); |
|
} |
|
if (count++ > 1024) { |
|
break; |
|
} |
|
} |
|
} |
|
} catch (Exception ex) { |
|
md.update((byte)ex.hashCode()); |
|
} |
|
|
|
|
|
Runtime rt = Runtime.getRuntime(); |
|
byte[] memBytes = longToByteArray(rt.totalMemory()); |
|
md.update(memBytes, 0, memBytes.length); |
|
memBytes = longToByteArray(rt.freeMemory()); |
|
md.update(memBytes, 0, memBytes.length); |
|
|
|
return null; |
|
} |
|
}); |
|
return md.digest(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static void addNetworkAdapterInfo(MessageDigest md) { |
|
|
|
try { |
|
Enumeration<NetworkInterface> ifcs = |
|
NetworkInterface.getNetworkInterfaces(); |
|
while (ifcs.hasMoreElements()) { |
|
NetworkInterface ifc = ifcs.nextElement(); |
|
md.update(ifc.toString().getBytes()); |
|
if (!ifc.isVirtual()) { |
|
byte[] bs = ifc.getHardwareAddress(); |
|
if (bs != null) { |
|
md.update(bs); |
|
break; |
|
} |
|
} |
|
} |
|
} catch (Exception ignore) { |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static byte[] longToByteArray(long l) { |
|
byte[] retVal = new byte[8]; |
|
|
|
for (int i=0; i<8; i++) { |
|
retVal[i] = (byte) l; |
|
l >>= 8; |
|
} |
|
|
|
return retVal; |
|
} |
|
|
|
/* |
|
// This method helps the test utility receive unprocessed seed bytes. |
|
public static int genTestSeed() { |
|
return myself.getByte(); |
|
} |
|
*/ |
|
|
|
|
|
private static class ThreadedSeedGenerator extends SeedGenerator |
|
implements Runnable { |
|
|
|
private byte[] pool; |
|
private int start, end, count; |
|
|
|
|
|
ThreadGroup seedGroup; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
ThreadedSeedGenerator() { |
|
pool = new byte[20]; |
|
start = end = 0; |
|
|
|
MessageDigest digest; |
|
|
|
try { |
|
digest = MessageDigest.getInstance("SHA"); |
|
} catch (NoSuchAlgorithmException e) { |
|
throw new InternalError("internal error: SHA-1 not available." |
|
, e); |
|
} |
|
|
|
final ThreadGroup[] finalsg = new ThreadGroup[1]; |
|
Thread t = java.security.AccessController.doPrivileged |
|
(new java.security.PrivilegedAction<>() { |
|
@Override |
|
public Thread run() { |
|
ThreadGroup parent, group = |
|
Thread.currentThread().getThreadGroup(); |
|
while ((parent = group.getParent()) != null) { |
|
group = parent; |
|
} |
|
finalsg[0] = new ThreadGroup |
|
(group, "SeedGenerator ThreadGroup"); |
|
Thread newT = new Thread(finalsg[0], |
|
ThreadedSeedGenerator.this, |
|
"SeedGenerator Thread", |
|
0, |
|
false); |
|
newT.setPriority(Thread.MIN_PRIORITY); |
|
newT.setDaemon(true); |
|
return newT; |
|
} |
|
}); |
|
seedGroup = finalsg[0]; |
|
t.start(); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public final void run() { |
|
try { |
|
while (true) { |
|
|
|
synchronized(this) { |
|
while (count >= pool.length) { |
|
wait(); |
|
} |
|
} |
|
|
|
int counter, quanta; |
|
byte v = 0; |
|
|
|
|
|
for (counter = quanta = 0; |
|
(counter < 64000) && (quanta < 6); quanta++) { |
|
|
|
|
|
try { |
|
BogusThread bt = new BogusThread(); |
|
Thread t = new Thread |
|
(seedGroup, bt, "SeedGenerator Thread", 0, |
|
false); |
|
t.start(); |
|
} catch (Exception e) { |
|
throw new InternalError("internal error: " + |
|
"SeedGenerator thread creation error.", e); |
|
} |
|
|
|
// We wait 250milli quanta, so the minimum wait time |
|
|
|
int latch = 0; |
|
long startTime = System.nanoTime(); |
|
while (System.nanoTime() - startTime < 250000000) { |
|
synchronized(this){}; |
|
|
|
latch = (latch + 1) & 0x1FFFFFFF; |
|
} |
|
|
|
// Translate the value using the permutation, and xor |
|
|
|
v ^= rndTab[latch % 255]; |
|
counter += latch; |
|
} |
|
|
|
// Push it into the queue and notify anybody who might |
|
|
|
synchronized(this) { |
|
pool[end] = v; |
|
end++; |
|
count++; |
|
if (end >= pool.length) { |
|
end = 0; |
|
} |
|
|
|
notifyAll(); |
|
} |
|
} |
|
} catch (Exception e) { |
|
throw new InternalError("internal error: " + |
|
"SeedGenerator thread generated an exception.", e); |
|
} |
|
} |
|
|
|
@Override |
|
void getSeedBytes(byte[] result) { |
|
for (int i = 0; i < result.length; i++) { |
|
result[i] = getSeedByte(); |
|
} |
|
} |
|
|
|
byte getSeedByte() { |
|
byte b; |
|
|
|
try { |
|
|
|
synchronized(this) { |
|
while (count <= 0) { |
|
wait(); |
|
} |
|
} |
|
} catch (Exception e) { |
|
if (count <= 0) { |
|
throw new InternalError("internal error: " + |
|
"SeedGenerator thread generated an exception.", e); |
|
} |
|
} |
|
|
|
synchronized(this) { |
|
|
|
b = pool[start]; |
|
pool[start] = 0; |
|
start++; |
|
count--; |
|
if (start == pool.length) { |
|
start = 0; |
|
} |
|
|
|
// Notify the daemon thread, just in case it is |
|
|
|
notifyAll(); |
|
} |
|
|
|
return b; |
|
} |
|
|
|
// The permutation was calculated by generating 64k of random |
|
// data and using it to mix the trivial permutation. |
|
// It should be evenly distributed. The specific values |
|
|
|
private static final byte[] rndTab = { |
|
56, 30, -107, -6, -86, 25, -83, 75, -12, -64, |
|
5, -128, 78, 21, 16, 32, 70, -81, 37, -51, |
|
-43, -46, -108, 87, 29, 17, -55, 22, -11, -111, |
|
-115, 84, -100, 108, -45, -15, -98, 72, -33, -28, |
|
31, -52, -37, -117, -97, -27, 93, -123, 47, 126, |
|
-80, -62, -93, -79, 61, -96, -65, -5, -47, -119, |
|
14, 89, 81, -118, -88, 20, 67, -126, -113, 60, |
|
-102, 55, 110, 28, 85, 121, 122, -58, 2, 45, |
|
43, 24, -9, 103, -13, 102, -68, -54, -101, -104, |
|
19, 13, -39, -26, -103, 62, 77, 51, 44, 111, |
|
73, 18, -127, -82, 4, -30, 11, -99, -74, 40, |
|
-89, 42, -76, -77, -94, -35, -69, 35, 120, 76, |
|
33, -73, -7, 82, -25, -10, 88, 125, -112, 58, |
|
83, 95, 6, 10, 98, -34, 80, 15, -91, 86, |
|
-19, 52, -17, 117, 49, -63, 118, -90, 36, -116, |
|
-40, -71, 97, -53, -109, -85, 109, -16, -3, 104, |
|
-95, 68, 54, 34, 26, 114, -1, 106, -121, 3, |
|
66, 0, 100, -84, 57, 107, 119, -42, 112, -61, |
|
1, 48, 38, 12, -56, -57, 39, -106, -72, 41, |
|
7, 71, -29, -59, -8, -38, 79, -31, 124, -124, |
|
8, 91, 116, 99, -4, 9, -36, -78, 63, -49, |
|
-67, -87, 59, 101, -32, 92, 94, 53, -41, 115, |
|
-66, -70, -122, 50, -50, -22, -20, -18, -21, 23, |
|
-2, -48, 96, 65, -105, 123, -14, -110, 69, -24, |
|
-120, -75, 74, 127, -60, 113, 90, -114, 105, 46, |
|
27, -125, -23, -44, 64 |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static class BogusThread implements Runnable { |
|
@Override |
|
public final void run() { |
|
try { |
|
for (int i = 0; i < 5; i++) { |
|
Thread.sleep(50); |
|
} |
|
// System.gc(); |
|
} catch (Exception e) { |
|
} |
|
} |
|
} |
|
} |
|
|
|
static class URLSeedGenerator extends SeedGenerator { |
|
|
|
private String deviceName; |
|
private InputStream seedStream; |
|
|
|
/** |
|
* The constructor is only called once to construct the one |
|
* instance we actually use. It opens the entropy gathering device |
|
* which will supply the randomness. |
|
*/ |
|
|
|
URLSeedGenerator(String egdurl) throws IOException { |
|
if (egdurl == null) { |
|
throw new IOException("No random source specified"); |
|
} |
|
deviceName = egdurl; |
|
init(); |
|
} |
|
|
|
private void init() throws IOException { |
|
final URL device = new URL(deviceName); |
|
try { |
|
seedStream = java.security.AccessController.doPrivileged |
|
(new java.security.PrivilegedExceptionAction<>() { |
|
@Override |
|
public InputStream run() throws IOException { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
if (device.getProtocol().equalsIgnoreCase("file")) { |
|
File deviceFile = |
|
SunEntries.getDeviceFile(device); |
|
return FileInputStreamPool |
|
.getInputStream(deviceFile); |
|
} else { |
|
return device.openStream(); |
|
} |
|
} |
|
}); |
|
} catch (Exception e) { |
|
throw new IOException( |
|
"Failed to open " + deviceName, e.getCause()); |
|
} |
|
} |
|
|
|
@Override |
|
void getSeedBytes(byte[] result) { |
|
int len = result.length; |
|
int read = 0; |
|
try { |
|
while (read < len) { |
|
int count = seedStream.read(result, read, len - read); |
|
|
|
if (count < 0) { |
|
throw new InternalError( |
|
"URLSeedGenerator " + deviceName + |
|
" reached end of file"); |
|
} |
|
read += count; |
|
} |
|
} catch (IOException ioe) { |
|
throw new InternalError("URLSeedGenerator " + deviceName + |
|
" generated exception: " + ioe.getMessage(), ioe); |
|
} |
|
} |
|
} |
|
} |