| /* | |
| * Copyright (c) 1996, 2024, 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.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; | |
| /** | |
| * 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; its 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 its different | |
| * activities, 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 | |
| */ | |
| abstract class SeedGenerator { | |
| // Static instance is created at link time | |
| private static SeedGenerator instance; | |
| private static final Debug debug = Debug.getInstance("provider"); | |
| // Static initializer to hook in selected or best performing generator | |
| static { | |
| String egdSource = SunEntries.getSeedSource(); | |
| /* | |
| * Try the URL specifying the source (e.g. file:/dev/random) | |
| * | |
| * The URLs "file:/dev/random" or "file:/dev/urandom" are used to | |
| * indicate the SeedGenerator should use OS support, if available. | |
| * | |
| * On Windows, this causes the MS CryptoAPI seeder to be used. | |
| * | |
| * On Solaris/Linux/MacOS, this is identical to using | |
| * URLSeedGenerator to read from /dev/[u]random | |
| */ | |
| 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.isEmpty()) { | |
| 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()); | |
| } | |
| } | |
| } | |
| // Fall back to ThreadedSeedGenerator | |
| if (instance == null) { | |
| if (debug != null) { | |
| debug.println("Using default threaded seed generator"); | |
| } | |
| instance = new ThreadedSeedGenerator(); | |
| } | |
| } | |
| /** | |
| * Fill result with bytes from the queue. Wait for it if it isn't ready. | |
| */ | |
| public static void generateSeed(byte[] result) { | |
| instance.getSeedBytes(result); | |
| } | |
| abstract void getSeedBytes(byte[] result); | |
| /** | |
| * Retrieve some system information, hashed. | |
| */ | |
| static byte[] getSystemEntropy() { | |
| final MessageDigest md; | |
| try { | |
| md = MessageDigest.getInstance("SHA"); | |
| } catch (NoSuchAlgorithmException nsae) { | |
| throw new InternalError("internal error: SHA-1 not available.", | |
| nsae); | |
| } | |
| // The current time in millis | |
| byte b =(byte)System.currentTimeMillis(); | |
| md.update(b); | |
| try { | |
| // System properties can change from machine to machine | |
| Properties p = System.getProperties(); | |
| for (String s: p.stringPropertyNames()) { | |
| md.update(s.getBytes()); | |
| md.update(p.getProperty(s).getBytes()); | |
| } | |
| // Include network adapter names (and a Mac address) | |
| addNetworkAdapterInfo(md); | |
| // The temporary dir | |
| 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 | |
| // always used. | |
| 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()); | |
| } | |
| // get Runtime memory stats | |
| 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 md.digest(); | |
| } | |
| /* | |
| * Include network adapter names and, if available, a Mac address | |
| * | |
| * See also java.util.concurrent.ThreadLocalRandom.initialSeed() | |
| */ | |
| 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()) { // skip fake addresses | |
| byte[] bs = ifc.getHardwareAddress(); | |
| if (bs != null) { | |
| md.update(bs); | |
| break; | |
| } | |
| } | |
| } | |
| } catch (Exception ignore) { | |
| } | |
| } | |
| /** | |
| * Helper function to convert a long into a byte array (least significant | |
| * byte first). | |
| */ | |
| 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 { | |
| // Queue is used to collect seed bytes | |
| private final byte[] pool; | |
| private int start, end, count; | |
| // Thread group for our threads | |
| ThreadGroup seedGroup; | |
| /** | |
| * The constructor is only called once to construct the one | |
| * instance we actually use. It instantiates the message digest | |
| * and starts the thread going. | |
| */ | |
| 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); | |
| } | |
| ThreadGroup[] finalsg = new ThreadGroup[1]; | |
| ThreadGroup parent, group = Thread.currentThread().getThreadGroup(); | |
| while ((parent = group.getParent()) != null) { | |
| group = parent; | |
| } | |
| finalsg[0] = new ThreadGroup(group, "SeedGenerator ThreadGroup"); | |
| Thread t = new Thread(finalsg[0], | |
| ThreadedSeedGenerator.this, | |
| "SeedGenerator Thread", | |
| 0, | |
| false); | |
| t.setPriority(Thread.MIN_PRIORITY); | |
| t.setDaemon(true); | |
| seedGroup = finalsg[0]; | |
| t.start(); | |
| } | |
| /** | |
| * This method does the actual work. It collects random bytes and | |
| * pushes them into the queue. | |
| */ | |
| @Override | |
| public final void run() { | |
| try { | |
| while (true) { | |
| // Queue full? Wait till there's room. | |
| synchronized(this) { | |
| while (count >= pool.length) { | |
| wait(); | |
| } | |
| } | |
| int counter, quanta; | |
| byte v = 0; | |
| // Spin count must not be under 64000 | |
| for (counter = quanta = 0; | |
| (counter < 64000) && (quanta < 6); quanta++) { | |
| // Start some noisy threads | |
| 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 | |
| // cannot be under 250milli. | |
| int latch = 0; | |
| long startTime = System.nanoTime(); | |
| while (System.nanoTime() - startTime < 250000000) { | |
| synchronized(this){} | |
| // Mask the sign bit and keep latch non-negative | |
| latch = (latch + 1) & 0x1FFFFFFF; | |
| } | |
| // Translate the value using the permutation, and xor | |
| // it with previous values gathered. | |
| v ^= rndTab[latch % 255]; | |
| counter += latch; | |
| } | |
| // Push it into the queue and notify anybody who might | |
| // be waiting for it. | |
| 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 { | |
| // Wait for it... | |
| 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) { | |
| // Get it from the queue | |
| b = pool[start]; | |
| pool[start] = 0; | |
| start++; | |
| count--; | |
| if (start == pool.length) { | |
| start = 0; | |
| } | |
| // Notify the daemon thread, just in case it is | |
| // waiting for us to make room in the queue. | |
| 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 | |
| // are not crucial to the security of this class. | |
| 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 | |
| }; | |
| /** | |
| * This inner thread causes the thread scheduler to become 'noisy', | |
| * thus adding entropy to the system load. | |
| * At least one instance of this class is generated for every seed byte. | |
| */ | |
| 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 final 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 { | |
| @SuppressWarnings("deprecation") | |
| URL device = new URL(deviceName); | |
| try { | |
| /* | |
| * return a shared InputStream for file URLs and | |
| * avoid buffering. | |
| * The URL.openStream() call wraps InputStream in a | |
| * BufferedInputStream which | |
| * can buffer up to 8K bytes. This read is a | |
| * performance issue for entropy sources which | |
| * can be slow to replenish. | |
| */ | |
| if (device.getProtocol().equalsIgnoreCase("file")) { | |
| File deviceFile = SunEntries.getDeviceFile(device); | |
| seedStream = FileInputStreamPool.getInputStream(deviceFile); | |
| } else { | |
| seedStream = 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); | |
| // /dev/random blocks - should never have EOF | |
| 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); | |
| } | |
| } | |
| } | |
| } |